Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
a4920d46
Commit
a4920d46
authored
Jun 17, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: attachment service part2
parent
bb5809ca
Changes
24
Hide whitespace changes
Inline
Side-by-side
Showing
24 changed files
with
702 additions
and
418 deletions
+702
-418
http_getter.go
plugin/httpgetter/http_getter.go
+0
-3
attachment.pb.go
proto/gen/store/attachment.pb.go
+287
-0
attachment.proto
proto/store/attachment.proto
+6
-6
acl_config.go
server/router/api/v1/acl_config.go
+1
-1
attachment_service.go
server/router/api/v1/attachment_service.go
+80
-80
memo_attachment_service.go
server/router/api/v1/memo_attachment_service.go
+22
-22
memo_service.go
server/router/api/v1/memo_service.go
+6
-6
rss.go
server/router/rss/rss.go
+8
-8
runner.go
server/runner/s3presign/runner.go
+17
-17
attachment.go
store/attachment.go
+31
-31
attachment.go
store/db/mysql/attachment.go
+27
-27
attachment.go
store/db/postgres/attachment.go
+24
-24
attachment.go
store/db/sqlite/attachment.go
+25
-25
driver.go
store/driver.go
+5
-5
attachment_test.go
store/test/attachment_test.go
+13
-13
AttachmentIcon.tsx
web/src/components/AttachmentIcon.tsx
+9
-9
MemoAttachment.tsx
web/src/components/MemoAttachment.tsx
+34
-0
MemoAttachmentListView.tsx
web/src/components/MemoAttachmentListView.tsx
+2
-2
UploadAttachmentButton.tsx
...onents/MemoEditor/ActionButton/UploadAttachmentButton.tsx
+92
-0
UploadResourceButton.tsx
...mponents/MemoEditor/ActionButton/UploadResourceButton.tsx
+0
-92
AttachmentListView.tsx
web/src/components/MemoEditor/AttachmentListView.tsx
+2
-2
index.tsx
web/src/components/MemoEditor/index.tsx
+8
-8
MemoResource.tsx
web/src/components/MemoResource.tsx
+0
-34
Attachments.tsx
web/src/pages/Attachments.tsx
+3
-3
No files found.
plugin/httpgetter/http_getter.go
View file @
a4920d46
// Package httpgetter is using to get resources from url.
// * Get metadata for website;
// * Get image blob to avoid CORS;
package
httpgetter
package
httpgetter
proto/gen/store/
resource
.pb.go
→
proto/gen/store/
attachment
.pb.go
View file @
a4920d46
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
// versions:
// versions:
// protoc-gen-go v1.36.6
// protoc-gen-go v1.36.6
// protoc (unknown)
// protoc (unknown)
// source: store/
resource
.proto
// source: store/
attachment
.proto
package
store
package
store
...
@@ -22,86 +22,86 @@ const (
...
@@ -22,86 +22,86 @@ const (
_
=
protoimpl
.
EnforceVersion
(
protoimpl
.
MaxVersion
-
20
)
_
=
protoimpl
.
EnforceVersion
(
protoimpl
.
MaxVersion
-
20
)
)
)
type
Resource
StorageType
int32
type
Attachment
StorageType
int32
const
(
const
(
ResourceStorageType_RESOURCE_STORAGE_TYPE_UNSPECIFIED
Resource
StorageType
=
0
AttachmentStorageType_ATTACHMENT_STORAGE_TYPE_UNSPECIFIED
Attachment
StorageType
=
0
//
Resource
is stored locally. AKA, local file system.
//
Attachment
is stored locally. AKA, local file system.
ResourceStorageType_LOCAL
Resource
StorageType
=
1
AttachmentStorageType_LOCAL
Attachment
StorageType
=
1
//
Resource
is stored in S3.
//
Attachment
is stored in S3.
ResourceStorageType_S3
Resource
StorageType
=
2
AttachmentStorageType_S3
Attachment
StorageType
=
2
//
Resource
is stored in an external storage. The reference is a URL.
//
Attachment
is stored in an external storage. The reference is a URL.
ResourceStorageType_EXTERNAL
Resource
StorageType
=
3
AttachmentStorageType_EXTERNAL
Attachment
StorageType
=
3
)
)
// Enum value maps for
Resource
StorageType.
// Enum value maps for
Attachment
StorageType.
var
(
var
(
Resource
StorageType_name
=
map
[
int32
]
string
{
Attachment
StorageType_name
=
map
[
int32
]
string
{
0
:
"
RESOURCE
_STORAGE_TYPE_UNSPECIFIED"
,
0
:
"
ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED"
,
1
:
"LOCAL"
,
1
:
"LOCAL"
,
2
:
"S3"
,
2
:
"S3"
,
3
:
"EXTERNAL"
,
3
:
"EXTERNAL"
,
}
}
Resource
StorageType_value
=
map
[
string
]
int32
{
Attachment
StorageType_value
=
map
[
string
]
int32
{
"
RESOURCE
_STORAGE_TYPE_UNSPECIFIED"
:
0
,
"
ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED"
:
0
,
"LOCAL"
:
1
,
"LOCAL"
:
1
,
"S3"
:
2
,
"S3"
:
2
,
"EXTERNAL"
:
3
,
"EXTERNAL"
:
3
,
}
}
)
)
func
(
x
ResourceStorageType
)
Enum
()
*
Resource
StorageType
{
func
(
x
AttachmentStorageType
)
Enum
()
*
Attachment
StorageType
{
p
:=
new
(
Resource
StorageType
)
p
:=
new
(
Attachment
StorageType
)
*
p
=
x
*
p
=
x
return
p
return
p
}
}
func
(
x
Resource
StorageType
)
String
()
string
{
func
(
x
Attachment
StorageType
)
String
()
string
{
return
protoimpl
.
X
.
EnumStringOf
(
x
.
Descriptor
(),
protoreflect
.
EnumNumber
(
x
))
return
protoimpl
.
X
.
EnumStringOf
(
x
.
Descriptor
(),
protoreflect
.
EnumNumber
(
x
))
}
}
func
(
Resource
StorageType
)
Descriptor
()
protoreflect
.
EnumDescriptor
{
func
(
Attachment
StorageType
)
Descriptor
()
protoreflect
.
EnumDescriptor
{
return
file_store_
resource
_proto_enumTypes
[
0
]
.
Descriptor
()
return
file_store_
attachment
_proto_enumTypes
[
0
]
.
Descriptor
()
}
}
func
(
Resource
StorageType
)
Type
()
protoreflect
.
EnumType
{
func
(
Attachment
StorageType
)
Type
()
protoreflect
.
EnumType
{
return
&
file_store_
resource
_proto_enumTypes
[
0
]
return
&
file_store_
attachment
_proto_enumTypes
[
0
]
}
}
func
(
x
Resource
StorageType
)
Number
()
protoreflect
.
EnumNumber
{
func
(
x
Attachment
StorageType
)
Number
()
protoreflect
.
EnumNumber
{
return
protoreflect
.
EnumNumber
(
x
)
return
protoreflect
.
EnumNumber
(
x
)
}
}
// Deprecated: Use
Resource
StorageType.Descriptor instead.
// Deprecated: Use
Attachment
StorageType.Descriptor instead.
func
(
Resource
StorageType
)
EnumDescriptor
()
([]
byte
,
[]
int
)
{
func
(
Attachment
StorageType
)
EnumDescriptor
()
([]
byte
,
[]
int
)
{
return
file_store_
resource
_proto_rawDescGZIP
(),
[]
int
{
0
}
return
file_store_
attachment
_proto_rawDescGZIP
(),
[]
int
{
0
}
}
}
type
Resource
Payload
struct
{
type
Attachment
Payload
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
state
protoimpl
.
MessageState
`protogen:"open.v1"`
// Types that are valid to be assigned to Payload:
// Types that are valid to be assigned to Payload:
//
//
// *
Resource
Payload_S3Object_
// *
Attachment
Payload_S3Object_
Payload
is
Resource
Payload_Payload
`protobuf_oneof:"payload"`
Payload
is
Attachment
Payload_Payload
`protobuf_oneof:"payload"`
unknownFields
protoimpl
.
UnknownFields
unknownFields
protoimpl
.
UnknownFields
sizeCache
protoimpl
.
SizeCache
sizeCache
protoimpl
.
SizeCache
}
}
func
(
x
*
Resource
Payload
)
Reset
()
{
func
(
x
*
Attachment
Payload
)
Reset
()
{
*
x
=
Resource
Payload
{}
*
x
=
Attachment
Payload
{}
mi
:=
&
file_store_
resource
_proto_msgTypes
[
0
]
mi
:=
&
file_store_
attachment
_proto_msgTypes
[
0
]
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
ms
.
StoreMessageInfo
(
mi
)
ms
.
StoreMessageInfo
(
mi
)
}
}
func
(
x
*
Resource
Payload
)
String
()
string
{
func
(
x
*
Attachment
Payload
)
String
()
string
{
return
protoimpl
.
X
.
MessageStringOf
(
x
)
return
protoimpl
.
X
.
MessageStringOf
(
x
)
}
}
func
(
*
Resource
Payload
)
ProtoMessage
()
{}
func
(
*
Attachment
Payload
)
ProtoMessage
()
{}
func
(
x
*
Resource
Payload
)
ProtoReflect
()
protoreflect
.
Message
{
func
(
x
*
Attachment
Payload
)
ProtoReflect
()
protoreflect
.
Message
{
mi
:=
&
file_store_
resource
_proto_msgTypes
[
0
]
mi
:=
&
file_store_
attachment
_proto_msgTypes
[
0
]
if
x
!=
nil
{
if
x
!=
nil
{
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
if
ms
.
LoadMessageInfo
()
==
nil
{
if
ms
.
LoadMessageInfo
()
==
nil
{
...
@@ -112,38 +112,38 @@ func (x *ResourcePayload) ProtoReflect() protoreflect.Message {
...
@@ -112,38 +112,38 @@ func (x *ResourcePayload) ProtoReflect() protoreflect.Message {
return
mi
.
MessageOf
(
x
)
return
mi
.
MessageOf
(
x
)
}
}
// Deprecated: Use
Resource
Payload.ProtoReflect.Descriptor instead.
// Deprecated: Use
Attachment
Payload.ProtoReflect.Descriptor instead.
func
(
*
Resource
Payload
)
Descriptor
()
([]
byte
,
[]
int
)
{
func
(
*
Attachment
Payload
)
Descriptor
()
([]
byte
,
[]
int
)
{
return
file_store_
resource
_proto_rawDescGZIP
(),
[]
int
{
0
}
return
file_store_
attachment
_proto_rawDescGZIP
(),
[]
int
{
0
}
}
}
func
(
x
*
ResourcePayload
)
GetPayload
()
isResource
Payload_Payload
{
func
(
x
*
AttachmentPayload
)
GetPayload
()
isAttachment
Payload_Payload
{
if
x
!=
nil
{
if
x
!=
nil
{
return
x
.
Payload
return
x
.
Payload
}
}
return
nil
return
nil
}
}
func
(
x
*
ResourcePayload
)
GetS3Object
()
*
Resource
Payload_S3Object
{
func
(
x
*
AttachmentPayload
)
GetS3Object
()
*
Attachment
Payload_S3Object
{
if
x
!=
nil
{
if
x
!=
nil
{
if
x
,
ok
:=
x
.
Payload
.
(
*
Resource
Payload_S3Object_
);
ok
{
if
x
,
ok
:=
x
.
Payload
.
(
*
Attachment
Payload_S3Object_
);
ok
{
return
x
.
S3Object
return
x
.
S3Object
}
}
}
}
return
nil
return
nil
}
}
type
is
Resource
Payload_Payload
interface
{
type
is
Attachment
Payload_Payload
interface
{
is
Resource
Payload_Payload
()
is
Attachment
Payload_Payload
()
}
}
type
Resource
Payload_S3Object_
struct
{
type
Attachment
Payload_S3Object_
struct
{
S3Object
*
Resource
Payload_S3Object
`protobuf:"bytes,1,opt,name=s3_object,json=s3Object,proto3,oneof"`
S3Object
*
Attachment
Payload_S3Object
`protobuf:"bytes,1,opt,name=s3_object,json=s3Object,proto3,oneof"`
}
}
func
(
*
ResourcePayload_S3Object_
)
isResource
Payload_Payload
()
{}
func
(
*
AttachmentPayload_S3Object_
)
isAttachment
Payload_Payload
()
{}
type
Resource
Payload_S3Object
struct
{
type
Attachment
Payload_S3Object
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
state
protoimpl
.
MessageState
`protogen:"open.v1"`
S3Config
*
StorageS3Config
`protobuf:"bytes,1,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
S3Config
*
StorageS3Config
`protobuf:"bytes,1,opt,name=s3_config,json=s3Config,proto3" json:"s3_config,omitempty"`
// key is the S3 object key.
// key is the S3 object key.
...
@@ -155,21 +155,21 @@ type ResourcePayload_S3Object struct {
...
@@ -155,21 +155,21 @@ type ResourcePayload_S3Object struct {
sizeCache
protoimpl
.
SizeCache
sizeCache
protoimpl
.
SizeCache
}
}
func
(
x
*
Resource
Payload_S3Object
)
Reset
()
{
func
(
x
*
Attachment
Payload_S3Object
)
Reset
()
{
*
x
=
Resource
Payload_S3Object
{}
*
x
=
Attachment
Payload_S3Object
{}
mi
:=
&
file_store_
resource
_proto_msgTypes
[
1
]
mi
:=
&
file_store_
attachment
_proto_msgTypes
[
1
]
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
ms
.
StoreMessageInfo
(
mi
)
ms
.
StoreMessageInfo
(
mi
)
}
}
func
(
x
*
Resource
Payload_S3Object
)
String
()
string
{
func
(
x
*
Attachment
Payload_S3Object
)
String
()
string
{
return
protoimpl
.
X
.
MessageStringOf
(
x
)
return
protoimpl
.
X
.
MessageStringOf
(
x
)
}
}
func
(
*
Resource
Payload_S3Object
)
ProtoMessage
()
{}
func
(
*
Attachment
Payload_S3Object
)
ProtoMessage
()
{}
func
(
x
*
Resource
Payload_S3Object
)
ProtoReflect
()
protoreflect
.
Message
{
func
(
x
*
Attachment
Payload_S3Object
)
ProtoReflect
()
protoreflect
.
Message
{
mi
:=
&
file_store_
resource
_proto_msgTypes
[
1
]
mi
:=
&
file_store_
attachment
_proto_msgTypes
[
1
]
if
x
!=
nil
{
if
x
!=
nil
{
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
ms
:=
protoimpl
.
X
.
MessageStateOf
(
protoimpl
.
Pointer
(
x
))
if
ms
.
LoadMessageInfo
()
==
nil
{
if
ms
.
LoadMessageInfo
()
==
nil
{
...
@@ -180,76 +180,76 @@ func (x *ResourcePayload_S3Object) ProtoReflect() protoreflect.Message {
...
@@ -180,76 +180,76 @@ func (x *ResourcePayload_S3Object) ProtoReflect() protoreflect.Message {
return
mi
.
MessageOf
(
x
)
return
mi
.
MessageOf
(
x
)
}
}
// Deprecated: Use
Resource
Payload_S3Object.ProtoReflect.Descriptor instead.
// Deprecated: Use
Attachment
Payload_S3Object.ProtoReflect.Descriptor instead.
func
(
*
Resource
Payload_S3Object
)
Descriptor
()
([]
byte
,
[]
int
)
{
func
(
*
Attachment
Payload_S3Object
)
Descriptor
()
([]
byte
,
[]
int
)
{
return
file_store_
resource
_proto_rawDescGZIP
(),
[]
int
{
0
,
0
}
return
file_store_
attachment
_proto_rawDescGZIP
(),
[]
int
{
0
,
0
}
}
}
func
(
x
*
Resource
Payload_S3Object
)
GetS3Config
()
*
StorageS3Config
{
func
(
x
*
Attachment
Payload_S3Object
)
GetS3Config
()
*
StorageS3Config
{
if
x
!=
nil
{
if
x
!=
nil
{
return
x
.
S3Config
return
x
.
S3Config
}
}
return
nil
return
nil
}
}
func
(
x
*
Resource
Payload_S3Object
)
GetKey
()
string
{
func
(
x
*
Attachment
Payload_S3Object
)
GetKey
()
string
{
if
x
!=
nil
{
if
x
!=
nil
{
return
x
.
Key
return
x
.
Key
}
}
return
""
return
""
}
}
func
(
x
*
Resource
Payload_S3Object
)
GetLastPresignedTime
()
*
timestamppb
.
Timestamp
{
func
(
x
*
Attachment
Payload_S3Object
)
GetLastPresignedTime
()
*
timestamppb
.
Timestamp
{
if
x
!=
nil
{
if
x
!=
nil
{
return
x
.
LastPresignedTime
return
x
.
LastPresignedTime
}
}
return
nil
return
nil
}
}
var
File_store_
resource
_proto
protoreflect
.
FileDescriptor
var
File_store_
attachment
_proto
protoreflect
.
FileDescriptor
const
file_store_
resource
_proto_rawDesc
=
""
+
const
file_store_
attachment
_proto_rawDesc
=
""
+
"
\n
"
+
"
\n
"
+
"
\x1
4
store/resource.proto
\x12\v
memos.store
\x1a\x1f
google/protobuf/timestamp.proto
\x1a\x1d
store/workspace_setting.proto
\"\x88
\x02\n
"
+
"
\x1
6
store/attachment.proto
\x12\v
memos.store
\x1a\x1f
google/protobuf/timestamp.proto
\x1a\x1d
store/workspace_setting.proto
\"\x8c
\x02\n
"
+
"
\x
0f
ResourcePayload
\x12
D
\n
"
+
"
\x
11
AttachmentPayload
\x12
F
\n
"
+
"
\t
s3_object
\x18\x01
\x01
(
\v
2
%.memos.store.Resource
Payload.S3ObjectH
\x00
R
\b
s3Object
\x1a\xa3\x01\n
"
+
"
\t
s3_object
\x18\x01
\x01
(
\v
2
'.memos.store.Attachment
Payload.S3ObjectH
\x00
R
\b
s3Object
\x1a\xa3\x01\n
"
+
"
\b
S3Object
\x12
9
\n
"
+
"
\b
S3Object
\x12
9
\n
"
+
"
\t
s3_config
\x18\x01
\x01
(
\v
2
\x1c
.memos.store.StorageS3ConfigR
\b
s3Config
\x12\x10\n
"
+
"
\t
s3_config
\x18\x01
\x01
(
\v
2
\x1c
.memos.store.StorageS3ConfigR
\b
s3Config
\x12\x10\n
"
+
"
\x03
key
\x18\x02
\x01
(
\t
R
\x03
key
\x12
J
\n
"
+
"
\x03
key
\x18\x02
\x01
(
\t
R
\x03
key
\x12
J
\n
"
+
"
\x13
last_presigned_time
\x18\x03
\x01
(
\v
2
\x1a
.google.protobuf.TimestampR
\x11
lastPresignedTimeB
\t\n
"
+
"
\x13
last_presigned_time
\x18\x03
\x01
(
\v
2
\x1a
.google.protobuf.TimestampR
\x11
lastPresignedTimeB
\t\n
"
+
"
\a
payload*
]
\n
"
+
"
\a
payload*
a
\n
"
+
"
\x1
3
ResourceStorageType
\x12
%
\n
"
+
"
\x1
5
AttachmentStorageType
\x12
'
\n
"
+
"
!RESOURCE
_STORAGE_TYPE_UNSPECIFIED
\x10\x00\x12\t\n
"
+
"
#ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED
\x10\x00\x12\t\n
"
+
"
\x05
LOCAL
\x10\x01\x12\x06\n
"
+
"
\x05
LOCAL
\x10\x01\x12\x06\n
"
+
"
\x02
S3
\x10\x02\x12\f\n
"
+
"
\x02
S3
\x10\x02\x12\f\n
"
+
"
\b
EXTERNAL
\x10\x03
B
\x9
8
\x01\n
"
+
"
\b
EXTERNAL
\x10\x03
B
\x9
a
\x01\n
"
+
"
\x0f
com.memos.storeB
\
r
Resource
ProtoP
\x01
Z)github.com/usememos/memos/proto/gen/store
\xa2\x02\x03
MSX
\xaa\x02\v
Memos.Store
\xca\x02\v
Memos
\\
Store
\xe2\x02\x17
Memos
\\
Store
\\
GPBMetadata
\xea\x02\f
Memos::Storeb
\x06
proto3"
"
\x0f
com.memos.storeB
\
x0f
Attachment
ProtoP
\x01
Z)github.com/usememos/memos/proto/gen/store
\xa2\x02\x03
MSX
\xaa\x02\v
Memos.Store
\xca\x02\v
Memos
\\
Store
\xe2\x02\x17
Memos
\\
Store
\\
GPBMetadata
\xea\x02\f
Memos::Storeb
\x06
proto3"
var
(
var
(
file_store_
resource
_proto_rawDescOnce
sync
.
Once
file_store_
attachment
_proto_rawDescOnce
sync
.
Once
file_store_
resource
_proto_rawDescData
[]
byte
file_store_
attachment
_proto_rawDescData
[]
byte
)
)
func
file_store_
resource
_proto_rawDescGZIP
()
[]
byte
{
func
file_store_
attachment
_proto_rawDescGZIP
()
[]
byte
{
file_store_
resource
_proto_rawDescOnce
.
Do
(
func
()
{
file_store_
attachment
_proto_rawDescOnce
.
Do
(
func
()
{
file_store_
resource_proto_rawDescData
=
protoimpl
.
X
.
CompressGZIP
(
unsafe
.
Slice
(
unsafe
.
StringData
(
file_store_resource_proto_rawDesc
),
len
(
file_store_resource
_proto_rawDesc
)))
file_store_
attachment_proto_rawDescData
=
protoimpl
.
X
.
CompressGZIP
(
unsafe
.
Slice
(
unsafe
.
StringData
(
file_store_attachment_proto_rawDesc
),
len
(
file_store_attachment
_proto_rawDesc
)))
})
})
return
file_store_
resource
_proto_rawDescData
return
file_store_
attachment
_proto_rawDescData
}
}
var
file_store_
resource
_proto_enumTypes
=
make
([]
protoimpl
.
EnumInfo
,
1
)
var
file_store_
attachment
_proto_enumTypes
=
make
([]
protoimpl
.
EnumInfo
,
1
)
var
file_store_
resource
_proto_msgTypes
=
make
([]
protoimpl
.
MessageInfo
,
2
)
var
file_store_
attachment
_proto_msgTypes
=
make
([]
protoimpl
.
MessageInfo
,
2
)
var
file_store_
resource
_proto_goTypes
=
[]
any
{
var
file_store_
attachment
_proto_goTypes
=
[]
any
{
(
ResourceStorageType
)(
0
),
// 0: memos.store.Resource
StorageType
(
AttachmentStorageType
)(
0
),
// 0: memos.store.Attachment
StorageType
(
*
ResourcePayload
)(
nil
),
// 1: memos.store.Resource
Payload
(
*
AttachmentPayload
)(
nil
),
// 1: memos.store.Attachment
Payload
(
*
ResourcePayload_S3Object
)(
nil
),
// 2: memos.store.Resource
Payload.S3Object
(
*
AttachmentPayload_S3Object
)(
nil
),
// 2: memos.store.Attachment
Payload.S3Object
(
*
StorageS3Config
)(
nil
),
// 3: memos.store.StorageS3Config
(
*
StorageS3Config
)(
nil
),
// 3: memos.store.StorageS3Config
(
*
timestamppb
.
Timestamp
)(
nil
),
// 4: google.protobuf.Timestamp
(
*
timestamppb
.
Timestamp
)(
nil
),
// 4: google.protobuf.Timestamp
}
}
var
file_store_
resource
_proto_depIdxs
=
[]
int32
{
var
file_store_
attachment
_proto_depIdxs
=
[]
int32
{
2
,
// 0: memos.store.
ResourcePayload.s3_object:type_name -> memos.store.Resource
Payload.S3Object
2
,
// 0: memos.store.
AttachmentPayload.s3_object:type_name -> memos.store.Attachment
Payload.S3Object
3
,
// 1: memos.store.
Resource
Payload.S3Object.s3_config:type_name -> memos.store.StorageS3Config
3
,
// 1: memos.store.
Attachment
Payload.S3Object.s3_config:type_name -> memos.store.StorageS3Config
4
,
// 2: memos.store.
Resource
Payload.S3Object.last_presigned_time:type_name -> google.protobuf.Timestamp
4
,
// 2: memos.store.
Attachment
Payload.S3Object.last_presigned_time:type_name -> google.protobuf.Timestamp
3
,
// [3:3] is the sub-list for method output_type
3
,
// [3:3] is the sub-list for method output_type
3
,
// [3:3] is the sub-list for method input_type
3
,
// [3:3] is the sub-list for method input_type
3
,
// [3:3] is the sub-list for extension type_name
3
,
// [3:3] is the sub-list for extension type_name
...
@@ -257,31 +257,31 @@ var file_store_resource_proto_depIdxs = []int32{
...
@@ -257,31 +257,31 @@ var file_store_resource_proto_depIdxs = []int32{
0
,
// [0:3] is the sub-list for field type_name
0
,
// [0:3] is the sub-list for field type_name
}
}
func
init
()
{
file_store_
resource
_proto_init
()
}
func
init
()
{
file_store_
attachment
_proto_init
()
}
func
file_store_
resource
_proto_init
()
{
func
file_store_
attachment
_proto_init
()
{
if
File_store_
resource
_proto
!=
nil
{
if
File_store_
attachment
_proto
!=
nil
{
return
return
}
}
file_store_workspace_setting_proto_init
()
file_store_workspace_setting_proto_init
()
file_store_
resource
_proto_msgTypes
[
0
]
.
OneofWrappers
=
[]
any
{
file_store_
attachment
_proto_msgTypes
[
0
]
.
OneofWrappers
=
[]
any
{
(
*
Resource
Payload_S3Object_
)(
nil
),
(
*
Attachment
Payload_S3Object_
)(
nil
),
}
}
type
x
struct
{}
type
x
struct
{}
out
:=
protoimpl
.
TypeBuilder
{
out
:=
protoimpl
.
TypeBuilder
{
File
:
protoimpl
.
DescBuilder
{
File
:
protoimpl
.
DescBuilder
{
GoPackagePath
:
reflect
.
TypeOf
(
x
{})
.
PkgPath
(),
GoPackagePath
:
reflect
.
TypeOf
(
x
{})
.
PkgPath
(),
RawDescriptor
:
unsafe
.
Slice
(
unsafe
.
StringData
(
file_store_
resource_proto_rawDesc
),
len
(
file_store_resource
_proto_rawDesc
)),
RawDescriptor
:
unsafe
.
Slice
(
unsafe
.
StringData
(
file_store_
attachment_proto_rawDesc
),
len
(
file_store_attachment
_proto_rawDesc
)),
NumEnums
:
1
,
NumEnums
:
1
,
NumMessages
:
2
,
NumMessages
:
2
,
NumExtensions
:
0
,
NumExtensions
:
0
,
NumServices
:
0
,
NumServices
:
0
,
},
},
GoTypes
:
file_store_
resource
_proto_goTypes
,
GoTypes
:
file_store_
attachment
_proto_goTypes
,
DependencyIndexes
:
file_store_
resource
_proto_depIdxs
,
DependencyIndexes
:
file_store_
attachment
_proto_depIdxs
,
EnumInfos
:
file_store_
resource
_proto_enumTypes
,
EnumInfos
:
file_store_
attachment
_proto_enumTypes
,
MessageInfos
:
file_store_
resource
_proto_msgTypes
,
MessageInfos
:
file_store_
attachment
_proto_msgTypes
,
}
.
Build
()
}
.
Build
()
File_store_
resource
_proto
=
out
.
File
File_store_
attachment
_proto
=
out
.
File
file_store_
resource
_proto_goTypes
=
nil
file_store_
attachment
_proto_goTypes
=
nil
file_store_
resource
_proto_depIdxs
=
nil
file_store_
attachment
_proto_depIdxs
=
nil
}
}
proto/store/
resource
.proto
→
proto/store/
attachment
.proto
View file @
a4920d46
...
@@ -7,17 +7,17 @@ import "store/workspace_setting.proto";
...
@@ -7,17 +7,17 @@ import "store/workspace_setting.proto";
option
go_package
=
"gen/store"
;
option
go_package
=
"gen/store"
;
enum
Resource
StorageType
{
enum
Attachment
StorageType
{
RESOURCE
_STORAGE_TYPE_UNSPECIFIED
=
0
;
ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED
=
0
;
//
Resource
is stored locally. AKA, local file system.
//
Attachment
is stored locally. AKA, local file system.
LOCAL
=
1
;
LOCAL
=
1
;
//
Resource
is stored in S3.
//
Attachment
is stored in S3.
S3
=
2
;
S3
=
2
;
//
Resource
is stored in an external storage. The reference is a URL.
//
Attachment
is stored in an external storage. The reference is a URL.
EXTERNAL
=
3
;
EXTERNAL
=
3
;
}
}
message
Resource
Payload
{
message
Attachment
Payload
{
oneof
payload
{
oneof
payload
{
S3Object
s3_object
=
1
;
S3Object
s3_object
=
1
;
}
}
...
...
server/router/api/v1/acl_config.go
View file @
a4920d46
...
@@ -18,7 +18,7 @@ var authenticationAllowlistMethods = map[string]bool{
...
@@ -18,7 +18,7 @@ var authenticationAllowlistMethods = map[string]bool{
"/memos.api.v1.MemoService/GetMemo"
:
true
,
"/memos.api.v1.MemoService/GetMemo"
:
true
,
"/memos.api.v1.MemoService/ListMemos"
:
true
,
"/memos.api.v1.MemoService/ListMemos"
:
true
,
"/memos.api.v1.MarkdownService/GetLinkMetadata"
:
true
,
"/memos.api.v1.MarkdownService/GetLinkMetadata"
:
true
,
"/memos.api.v1.
ResourceService/GetResourceBinary"
:
true
,
"/memos.api.v1.
AttachmentService/GetAttachmentBinary"
:
true
,
}
}
// isUnauthorizeAllowedMethod returns whether the method is exempted from authentication.
// isUnauthorizeAllowedMethod returns whether the method is exempted from authentication.
...
...
server/router/api/v1/attachment_service.go
View file @
a4920d46
...
@@ -68,7 +68,7 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
...
@@ -68,7 +68,7 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
attachmentUID
=
shortuuid
.
New
()
attachmentUID
=
shortuuid
.
New
()
}
}
create
:=
&
store
.
Resource
{
create
:=
&
store
.
Attachment
{
UID
:
attachmentUID
,
UID
:
attachmentUID
,
CreatorID
:
user
.
ID
,
CreatorID
:
user
.
ID
,
Filename
:
request
.
Attachment
.
Filename
,
Filename
:
request
.
Attachment
.
Filename
,
...
@@ -90,8 +90,8 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
...
@@ -90,8 +90,8 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
create
.
Size
=
int64
(
size
)
create
.
Size
=
int64
(
size
)
create
.
Blob
=
request
.
Attachment
.
Content
create
.
Blob
=
request
.
Attachment
.
Content
if
err
:=
Save
Resource
Blob
(
ctx
,
s
.
Profile
,
s
.
Store
,
create
);
err
!=
nil
{
if
err
:=
Save
Attachment
Blob
(
ctx
,
s
.
Profile
,
s
.
Store
,
create
);
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to save
resource
blob: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to save
attachment
blob: %v"
,
err
)
}
}
if
request
.
Attachment
.
Memo
!=
nil
{
if
request
.
Attachment
.
Memo
!=
nil
{
...
@@ -108,12 +108,12 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
...
@@ -108,12 +108,12 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
}
}
create
.
MemoID
=
&
memo
.
ID
create
.
MemoID
=
&
memo
.
ID
}
}
resource
,
err
:=
s
.
Store
.
CreateResource
(
ctx
,
create
)
attachment
,
err
:=
s
.
Store
.
CreateAttachment
(
ctx
,
create
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to create
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to create
attachment
: %v"
,
err
)
}
}
return
s
.
convertAttachmentFromStore
(
ctx
,
resource
),
nil
return
s
.
convertAttachmentFromStore
(
ctx
,
attachment
),
nil
}
}
func
(
s
*
APIV1Service
)
ListAttachments
(
ctx
context
.
Context
,
request
*
v1pb
.
ListAttachmentsRequest
)
(
*
v1pb
.
ListAttachmentsResponse
,
error
)
{
func
(
s
*
APIV1Service
)
ListAttachments
(
ctx
context
.
Context
,
request
*
v1pb
.
ListAttachmentsRequest
)
(
*
v1pb
.
ListAttachmentsResponse
,
error
)
{
...
@@ -141,7 +141,7 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt
...
@@ -141,7 +141,7 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt
}
}
}
}
find
Resource
:=
&
store
.
FindResource
{
find
Attachment
:=
&
store
.
FindAttachment
{
CreatorID
:
&
user
.
ID
,
CreatorID
:
&
user
.
ID
,
Limit
:
&
pageSize
,
Limit
:
&
pageSize
,
Offset
:
&
offset
,
Offset
:
&
offset
,
...
@@ -154,40 +154,40 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt
...
@@ -154,40 +154,40 @@ func (s *APIV1Service) ListAttachments(ctx context.Context, request *v1pb.ListAt
if
strings
.
HasPrefix
(
request
.
Filter
,
"type="
)
{
if
strings
.
HasPrefix
(
request
.
Filter
,
"type="
)
{
filterType
:=
strings
.
TrimPrefix
(
request
.
Filter
,
"type="
)
filterType
:=
strings
.
TrimPrefix
(
request
.
Filter
,
"type="
)
// Create a temporary struct to hold type filter
// Create a temporary struct to hold type filter
// Since Find
Resource
doesn't have Type field, we'll apply this post-query
// Since Find
Attachment
doesn't have Type field, we'll apply this post-query
_
=
filterType
// We'll filter after getting results
_
=
filterType
// We'll filter after getting results
}
}
}
}
resources
,
err
:=
s
.
Store
.
ListResources
(
ctx
,
findResource
)
attachments
,
err
:=
s
.
Store
.
ListAttachments
(
ctx
,
findAttachment
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
resource
s: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
attachment
s: %v"
,
err
)
}
}
// Apply type filter if specified
// Apply type filter if specified
if
request
.
Filter
!=
""
&&
strings
.
HasPrefix
(
request
.
Filter
,
"type="
)
{
if
request
.
Filter
!=
""
&&
strings
.
HasPrefix
(
request
.
Filter
,
"type="
)
{
filterType
:=
strings
.
TrimPrefix
(
request
.
Filter
,
"type="
)
filterType
:=
strings
.
TrimPrefix
(
request
.
Filter
,
"type="
)
filtered
Resources
:=
make
([]
*
store
.
Resource
,
0
)
filtered
Attachments
:=
make
([]
*
store
.
Attachment
,
0
)
for
_
,
resource
:=
range
resource
s
{
for
_
,
attachment
:=
range
attachment
s
{
if
resource
.
Type
==
filterType
{
if
attachment
.
Type
==
filterType
{
filtered
Resources
=
append
(
filteredResources
,
resource
)
filtered
Attachments
=
append
(
filteredAttachments
,
attachment
)
}
}
}
}
resources
=
filteredResource
s
attachments
=
filteredAttachment
s
}
}
response
:=
&
v1pb
.
ListAttachmentsResponse
{}
response
:=
&
v1pb
.
ListAttachmentsResponse
{}
for
_
,
resource
:=
range
resource
s
{
for
_
,
attachment
:=
range
attachment
s
{
response
.
Attachments
=
append
(
response
.
Attachments
,
s
.
convertAttachmentFromStore
(
ctx
,
resource
))
response
.
Attachments
=
append
(
response
.
Attachments
,
s
.
convertAttachmentFromStore
(
ctx
,
attachment
))
}
}
// For simplicity, set total size to the number of returned
resources
// For simplicity, set total size to the number of returned
attachments.
// In a full implementation, you'd want a separate count query
// In a full implementation, you'd want a separate count query
response
.
TotalSize
=
int32
(
len
(
response
.
Attachments
))
response
.
TotalSize
=
int32
(
len
(
response
.
Attachments
))
// Set next page token if we got the full page size (indicating there might be more)
// Set next page token if we got the full page size (indicating there might be more)
if
len
(
resource
s
)
==
pageSize
{
if
len
(
attachment
s
)
==
pageSize
{
response
.
NextPageToken
=
fmt
.
Sprintf
(
"%d"
,
offset
+
pageSize
)
response
.
NextPageToken
=
fmt
.
Sprintf
(
"%d"
,
offset
+
pageSize
)
}
}
...
@@ -199,14 +199,14 @@ func (s *APIV1Service) GetAttachment(ctx context.Context, request *v1pb.GetAttac
...
@@ -199,14 +199,14 @@ func (s *APIV1Service) GetAttachment(ctx context.Context, request *v1pb.GetAttac
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment id: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment id: %v"
,
err
)
}
}
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
UID
:
&
attachmentUID
})
attachment
,
err
:=
s
.
Store
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
UID
:
&
attachmentUID
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
attachment
: %v"
,
err
)
}
}
if
resource
==
nil
{
if
attachment
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"attachment not found"
)
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"attachment not found"
)
}
}
return
s
.
convertAttachmentFromStore
(
ctx
,
resource
),
nil
return
s
.
convertAttachmentFromStore
(
ctx
,
attachment
),
nil
}
}
func
(
s
*
APIV1Service
)
GetAttachmentBinary
(
ctx
context
.
Context
,
request
*
v1pb
.
GetAttachmentBinaryRequest
)
(
*
httpbody
.
HttpBody
,
error
)
{
func
(
s
*
APIV1Service
)
GetAttachmentBinary
(
ctx
context
.
Context
,
request
*
v1pb
.
GetAttachmentBinaryRequest
)
(
*
httpbody
.
HttpBody
,
error
)
{
...
@@ -214,23 +214,23 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
...
@@ -214,23 +214,23 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment id: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment id: %v"
,
err
)
}
}
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
attachment
,
err
:=
s
.
Store
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
GetBlob
:
true
,
GetBlob
:
true
,
UID
:
&
attachmentUID
,
UID
:
&
attachmentUID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
attachment
: %v"
,
err
)
}
}
if
resource
==
nil
{
if
attachment
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"attachment not found"
)
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"attachment not found"
)
}
}
// Check the related memo visibility.
// Check the related memo visibility.
if
resource
.
MemoID
!=
nil
{
if
attachment
.
MemoID
!=
nil
{
memo
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
memo
,
err
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
ID
:
resource
.
MemoID
,
ID
:
attachment
.
MemoID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to find memo by ID: %v"
,
resource
.
MemoID
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to find memo by ID: %v"
,
attachment
.
MemoID
)
}
}
if
memo
!=
nil
&&
memo
.
Visibility
!=
store
.
Public
{
if
memo
!=
nil
&&
memo
.
Visibility
!=
store
.
Public
{
user
,
err
:=
s
.
GetCurrentUser
(
ctx
)
user
,
err
:=
s
.
GetCurrentUser
(
ctx
)
...
@@ -240,32 +240,32 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
...
@@ -240,32 +240,32 @@ func (s *APIV1Service) GetAttachmentBinary(ctx context.Context, request *v1pb.Ge
if
user
==
nil
{
if
user
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"unauthorized access"
)
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"unauthorized access"
)
}
}
if
memo
.
Visibility
==
store
.
Private
&&
user
.
ID
!=
resource
.
CreatorID
{
if
memo
.
Visibility
==
store
.
Private
&&
user
.
ID
!=
attachment
.
CreatorID
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"unauthorized access"
)
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"unauthorized access"
)
}
}
}
}
}
}
if
request
.
Thumbnail
&&
util
.
HasPrefixes
(
resource
.
Type
,
SupportedThumbnailMimeTypes
...
)
{
if
request
.
Thumbnail
&&
util
.
HasPrefixes
(
attachment
.
Type
,
SupportedThumbnailMimeTypes
...
)
{
thumbnailBlob
,
err
:=
s
.
getOrGenerateThumbnail
(
resource
)
thumbnailBlob
,
err
:=
s
.
getOrGenerateThumbnail
(
attachment
)
if
err
!=
nil
{
if
err
!=
nil
{
// thumbnail failures are logged as warnings and not cosidered critical failures as
// thumbnail failures are logged as warnings and not cosidered critical failures as
// a
resource
image can be used in its place.
// a
attachment
image can be used in its place.
slog
.
Warn
(
"failed to get
resource
thumbnail image"
,
slog
.
Any
(
"error"
,
err
))
slog
.
Warn
(
"failed to get
attachment
thumbnail image"
,
slog
.
Any
(
"error"
,
err
))
}
else
{
}
else
{
return
&
httpbody
.
HttpBody
{
return
&
httpbody
.
HttpBody
{
ContentType
:
resource
.
Type
,
ContentType
:
attachment
.
Type
,
Data
:
thumbnailBlob
,
Data
:
thumbnailBlob
,
},
nil
},
nil
}
}
}
}
blob
,
err
:=
s
.
Get
ResourceBlob
(
resource
)
blob
,
err
:=
s
.
Get
AttachmentBlob
(
attachment
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
resource
blob: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
attachment
blob: %v"
,
err
)
}
}
contentType
:=
resource
.
Type
contentType
:=
attachment
.
Type
if
strings
.
HasPrefix
(
contentType
,
"text/"
)
{
if
strings
.
HasPrefix
(
contentType
,
"text/"
)
{
contentType
+=
"; charset=utf-8"
contentType
+=
"; charset=utf-8"
}
}
...
@@ -290,14 +290,14 @@ func (s *APIV1Service) UpdateAttachment(ctx context.Context, request *v1pb.Updat
...
@@ -290,14 +290,14 @@ func (s *APIV1Service) UpdateAttachment(ctx context.Context, request *v1pb.Updat
if
request
.
UpdateMask
==
nil
||
len
(
request
.
UpdateMask
.
Paths
)
==
0
{
if
request
.
UpdateMask
==
nil
||
len
(
request
.
UpdateMask
.
Paths
)
==
0
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"update mask is required"
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"update mask is required"
)
}
}
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
UID
:
&
attachmentUID
})
attachment
,
err
:=
s
.
Store
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
UID
:
&
attachmentUID
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
attachment
: %v"
,
err
)
}
}
currentTs
:=
time
.
Now
()
.
Unix
()
currentTs
:=
time
.
Now
()
.
Unix
()
update
:=
&
store
.
Update
Resource
{
update
:=
&
store
.
Update
Attachment
{
ID
:
resource
.
ID
,
ID
:
attachment
.
ID
,
UpdatedTs
:
&
currentTs
,
UpdatedTs
:
&
currentTs
,
}
}
for
_
,
field
:=
range
request
.
UpdateMask
.
Paths
{
for
_
,
field
:=
range
request
.
UpdateMask
.
Paths
{
...
@@ -306,8 +306,8 @@ func (s *APIV1Service) UpdateAttachment(ctx context.Context, request *v1pb.Updat
...
@@ -306,8 +306,8 @@ func (s *APIV1Service) UpdateAttachment(ctx context.Context, request *v1pb.Updat
}
}
}
}
if
err
:=
s
.
Store
.
Update
Resource
(
ctx
,
update
);
err
!=
nil
{
if
err
:=
s
.
Store
.
Update
Attachment
(
ctx
,
update
);
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to update
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to update
attachment
: %v"
,
err
)
}
}
return
s
.
GetAttachment
(
ctx
,
&
v1pb
.
GetAttachmentRequest
{
return
s
.
GetAttachment
(
ctx
,
&
v1pb
.
GetAttachmentRequest
{
Name
:
request
.
Attachment
.
Name
,
Name
:
request
.
Attachment
.
Name
,
...
@@ -323,39 +323,39 @@ func (s *APIV1Service) DeleteAttachment(ctx context.Context, request *v1pb.Delet
...
@@ -323,39 +323,39 @@ func (s *APIV1Service) DeleteAttachment(ctx context.Context, request *v1pb.Delet
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user: %v"
,
err
)
}
}
resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
attachment
,
err
:=
s
.
Store
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
UID
:
&
attachmentUID
,
UID
:
&
attachmentUID
,
CreatorID
:
&
user
.
ID
,
CreatorID
:
&
user
.
ID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to find
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to find
attachment
: %v"
,
err
)
}
}
if
resource
==
nil
{
if
attachment
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"attachment not found"
)
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"attachment not found"
)
}
}
// Delete the
resource
from the database.
// Delete the
attachment
from the database.
if
err
:=
s
.
Store
.
Delete
Resource
(
ctx
,
&
store
.
DeleteResource
{
if
err
:=
s
.
Store
.
Delete
Attachment
(
ctx
,
&
store
.
DeleteAttachment
{
ID
:
resource
.
ID
,
ID
:
attachment
.
ID
,
});
err
!=
nil
{
});
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete
attachment
: %v"
,
err
)
}
}
return
&
emptypb
.
Empty
{},
nil
return
&
emptypb
.
Empty
{},
nil
}
}
func
(
s
*
APIV1Service
)
convertAttachmentFromStore
(
ctx
context
.
Context
,
resource
*
store
.
Resource
)
*
v1pb
.
Attachment
{
func
(
s
*
APIV1Service
)
convertAttachmentFromStore
(
ctx
context
.
Context
,
attachment
*
store
.
Attachment
)
*
v1pb
.
Attachment
{
attachmentMessage
:=
&
v1pb
.
Attachment
{
attachmentMessage
:=
&
v1pb
.
Attachment
{
Name
:
fmt
.
Sprintf
(
"%s%s"
,
AttachmentNamePrefix
,
resource
.
UID
),
Name
:
fmt
.
Sprintf
(
"%s%s"
,
AttachmentNamePrefix
,
attachment
.
UID
),
CreateTime
:
timestamppb
.
New
(
time
.
Unix
(
resource
.
CreatedTs
,
0
)),
CreateTime
:
timestamppb
.
New
(
time
.
Unix
(
attachment
.
CreatedTs
,
0
)),
Filename
:
resource
.
Filename
,
Filename
:
attachment
.
Filename
,
Type
:
resource
.
Type
,
Type
:
attachment
.
Type
,
Size
:
resource
.
Size
,
Size
:
attachment
.
Size
,
}
}
if
resource
.
StorageType
==
storepb
.
ResourceStorageType_EXTERNAL
||
resource
.
StorageType
==
storepb
.
Resource
StorageType_S3
{
if
attachment
.
StorageType
==
storepb
.
AttachmentStorageType_EXTERNAL
||
attachment
.
StorageType
==
storepb
.
Attachment
StorageType_S3
{
attachmentMessage
.
ExternalLink
=
resource
.
Reference
attachmentMessage
.
ExternalLink
=
attachment
.
Reference
}
}
if
resource
.
MemoID
!=
nil
{
if
attachment
.
MemoID
!=
nil
{
memo
,
_
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
memo
,
_
:=
s
.
Store
.
GetMemo
(
ctx
,
&
store
.
FindMemo
{
ID
:
resource
.
MemoID
,
ID
:
attachment
.
MemoID
,
})
})
if
memo
!=
nil
{
if
memo
!=
nil
{
memoName
:=
fmt
.
Sprintf
(
"%s%s"
,
MemoNamePrefix
,
memo
.
UID
)
memoName
:=
fmt
.
Sprintf
(
"%s%s"
,
MemoNamePrefix
,
memo
.
UID
)
...
@@ -366,8 +366,8 @@ func (s *APIV1Service) convertAttachmentFromStore(ctx context.Context, resource
...
@@ -366,8 +366,8 @@ func (s *APIV1Service) convertAttachmentFromStore(ctx context.Context, resource
return
attachmentMessage
return
attachmentMessage
}
}
// Save
ResourceBlob save the blob of resource
based on the storage config.
// Save
AttachmentBlob save the blob of attachment
based on the storage config.
func
Save
ResourceBlob
(
ctx
context
.
Context
,
profile
*
profile
.
Profile
,
stores
*
store
.
Store
,
create
*
store
.
Resource
)
error
{
func
Save
AttachmentBlob
(
ctx
context
.
Context
,
profile
*
profile
.
Profile
,
stores
*
store
.
Store
,
create
*
store
.
Attachment
)
error
{
workspaceStorageSetting
,
err
:=
stores
.
GetWorkspaceStorageSetting
(
ctx
)
workspaceStorageSetting
,
err
:=
stores
.
GetWorkspaceStorageSetting
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"Failed to find workspace storage setting"
)
return
errors
.
Wrap
(
err
,
"Failed to find workspace storage setting"
)
...
@@ -407,7 +407,7 @@ func SaveResourceBlob(ctx context.Context, profile *profile.Profile, stores *sto
...
@@ -407,7 +407,7 @@ func SaveResourceBlob(ctx context.Context, profile *profile.Profile, stores *sto
}
}
create
.
Reference
=
internalPath
create
.
Reference
=
internalPath
create
.
Blob
=
nil
create
.
Blob
=
nil
create
.
StorageType
=
storepb
.
Resource
StorageType_LOCAL
create
.
StorageType
=
storepb
.
Attachment
StorageType_LOCAL
}
else
if
workspaceStorageSetting
.
StorageType
==
storepb
.
WorkspaceStorageSetting_S3
{
}
else
if
workspaceStorageSetting
.
StorageType
==
storepb
.
WorkspaceStorageSetting_S3
{
s3Config
:=
workspaceStorageSetting
.
S3Config
s3Config
:=
workspaceStorageSetting
.
S3Config
if
s3Config
==
nil
{
if
s3Config
==
nil
{
...
@@ -434,10 +434,10 @@ func SaveResourceBlob(ctx context.Context, profile *profile.Profile, stores *sto
...
@@ -434,10 +434,10 @@ func SaveResourceBlob(ctx context.Context, profile *profile.Profile, stores *sto
create
.
Reference
=
presignURL
create
.
Reference
=
presignURL
create
.
Blob
=
nil
create
.
Blob
=
nil
create
.
StorageType
=
storepb
.
Resource
StorageType_S3
create
.
StorageType
=
storepb
.
Attachment
StorageType_S3
create
.
Payload
=
&
storepb
.
Resource
Payload
{
create
.
Payload
=
&
storepb
.
Attachment
Payload
{
Payload
:
&
storepb
.
Resource
Payload_S3Object_
{
Payload
:
&
storepb
.
Attachment
Payload_S3Object_
{
S3Object
:
&
storepb
.
Resource
Payload_S3Object
{
S3Object
:
&
storepb
.
Attachment
Payload_S3Object
{
S3Config
:
s3Config
,
S3Config
:
s3Config
,
Key
:
key
,
Key
:
key
,
LastPresignedTime
:
timestamppb
.
New
(
time
.
Now
()),
LastPresignedTime
:
timestamppb
.
New
(
time
.
Now
()),
...
@@ -449,15 +449,15 @@ func SaveResourceBlob(ctx context.Context, profile *profile.Profile, stores *sto
...
@@ -449,15 +449,15 @@ func SaveResourceBlob(ctx context.Context, profile *profile.Profile, stores *sto
return
nil
return
nil
}
}
func
(
s
*
APIV1Service
)
Get
ResourceBlob
(
resource
*
store
.
Resource
)
([]
byte
,
error
)
{
func
(
s
*
APIV1Service
)
Get
AttachmentBlob
(
attachment
*
store
.
Attachment
)
([]
byte
,
error
)
{
// For local storage, read the file from the local disk.
// For local storage, read the file from the local disk.
if
resource
.
StorageType
==
storepb
.
Resource
StorageType_LOCAL
{
if
attachment
.
StorageType
==
storepb
.
Attachment
StorageType_LOCAL
{
resourcePath
:=
filepath
.
FromSlash
(
resource
.
Reference
)
attachmentPath
:=
filepath
.
FromSlash
(
attachment
.
Reference
)
if
!
filepath
.
IsAbs
(
resource
Path
)
{
if
!
filepath
.
IsAbs
(
attachment
Path
)
{
resourcePath
=
filepath
.
Join
(
s
.
Profile
.
Data
,
resource
Path
)
attachmentPath
=
filepath
.
Join
(
s
.
Profile
.
Data
,
attachment
Path
)
}
}
file
,
err
:=
os
.
Open
(
resource
Path
)
file
,
err
:=
os
.
Open
(
attachment
Path
)
if
err
!=
nil
{
if
err
!=
nil
{
if
os
.
IsNotExist
(
err
)
{
if
os
.
IsNotExist
(
err
)
{
return
nil
,
errors
.
Wrap
(
err
,
"file not found"
)
return
nil
,
errors
.
Wrap
(
err
,
"file not found"
)
...
@@ -472,7 +472,7 @@ func (s *APIV1Service) GetResourceBlob(resource *store.Resource) ([]byte, error)
...
@@ -472,7 +472,7 @@ func (s *APIV1Service) GetResourceBlob(resource *store.Resource) ([]byte, error)
return
blob
,
nil
return
blob
,
nil
}
}
// For database storage, return the blob from the database.
// For database storage, return the blob from the database.
return
resource
.
Blob
,
nil
return
attachment
.
Blob
,
nil
}
}
const
(
const
(
...
@@ -480,22 +480,22 @@ const (
...
@@ -480,22 +480,22 @@ const (
thumbnailRatio
=
0.8
thumbnailRatio
=
0.8
)
)
// getOrGenerateThumbnail returns the thumbnail image of the
resource
.
// getOrGenerateThumbnail returns the thumbnail image of the
attachment
.
func
(
s
*
APIV1Service
)
getOrGenerateThumbnail
(
resource
*
store
.
Resource
)
([]
byte
,
error
)
{
func
(
s
*
APIV1Service
)
getOrGenerateThumbnail
(
attachment
*
store
.
Attachment
)
([]
byte
,
error
)
{
thumbnailCacheFolder
:=
filepath
.
Join
(
s
.
Profile
.
Data
,
ThumbnailCacheFolder
)
thumbnailCacheFolder
:=
filepath
.
Join
(
s
.
Profile
.
Data
,
ThumbnailCacheFolder
)
if
err
:=
os
.
MkdirAll
(
thumbnailCacheFolder
,
os
.
ModePerm
);
err
!=
nil
{
if
err
:=
os
.
MkdirAll
(
thumbnailCacheFolder
,
os
.
ModePerm
);
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to create thumbnail cache folder"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to create thumbnail cache folder"
)
}
}
filePath
:=
filepath
.
Join
(
thumbnailCacheFolder
,
fmt
.
Sprintf
(
"%d%s"
,
resource
.
ID
,
filepath
.
Ext
(
resource
.
Filename
)))
filePath
:=
filepath
.
Join
(
thumbnailCacheFolder
,
fmt
.
Sprintf
(
"%d%s"
,
attachment
.
ID
,
filepath
.
Ext
(
attachment
.
Filename
)))
if
_
,
err
:=
os
.
Stat
(
filePath
);
err
!=
nil
{
if
_
,
err
:=
os
.
Stat
(
filePath
);
err
!=
nil
{
if
!
os
.
IsNotExist
(
err
)
{
if
!
os
.
IsNotExist
(
err
)
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to check thumbnail image stat"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to check thumbnail image stat"
)
}
}
// If thumbnail image does not exist, generate and save the thumbnail image.
// If thumbnail image does not exist, generate and save the thumbnail image.
blob
,
err
:=
s
.
Get
ResourceBlob
(
resource
)
blob
,
err
:=
s
.
Get
AttachmentBlob
(
attachment
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to get
resource
blob"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to get
attachment
blob"
)
}
}
img
,
err
:=
imaging
.
Decode
(
bytes
.
NewReader
(
blob
),
imaging
.
AutoOrientation
(
true
))
img
,
err
:=
imaging
.
Decode
(
bytes
.
NewReader
(
blob
),
imaging
.
AutoOrientation
(
true
))
if
err
!=
nil
{
if
err
!=
nil
{
...
...
server/router/api/v1/memo_attachment_service.go
View file @
a4920d46
...
@@ -22,54 +22,54 @@ func (s *APIV1Service) SetMemoAttachments(ctx context.Context, request *v1pb.Set
...
@@ -22,54 +22,54 @@ func (s *APIV1Service) SetMemoAttachments(ctx context.Context, request *v1pb.Set
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get memo"
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get memo"
)
}
}
resources
,
err
:=
s
.
Store
.
ListResources
(
ctx
,
&
store
.
FindResource
{
attachments
,
err
:=
s
.
Store
.
ListAttachments
(
ctx
,
&
store
.
FindAttachment
{
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
.
ID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
resource
s"
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
attachment
s"
)
}
}
// Delete
resource
s that are not in the request.
// Delete
attachment
s that are not in the request.
for
_
,
resource
:=
range
resource
s
{
for
_
,
attachment
:=
range
attachment
s
{
found
:=
false
found
:=
false
for
_
,
request
Resource
:=
range
request
.
Attachments
{
for
_
,
request
Attachment
:=
range
request
.
Attachments
{
request
ResourceUID
,
err
:=
ExtractAttachmentUIDFromName
(
requestResource
.
Name
)
request
AttachmentUID
,
err
:=
ExtractAttachmentUIDFromName
(
requestAttachment
.
Name
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment name: %v"
,
err
)
}
}
if
resource
.
UID
==
requestResource
UID
{
if
attachment
.
UID
==
requestAttachment
UID
{
found
=
true
found
=
true
break
break
}
}
}
}
if
!
found
{
if
!
found
{
if
err
=
s
.
Store
.
Delete
Resource
(
ctx
,
&
store
.
DeleteResource
{
if
err
=
s
.
Store
.
Delete
Attachment
(
ctx
,
&
store
.
DeleteAttachment
{
ID
:
int32
(
resource
.
ID
),
ID
:
int32
(
attachment
.
ID
),
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
.
ID
,
});
err
!=
nil
{
});
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete
resource
"
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete
attachment
"
)
}
}
}
}
}
}
slices
.
Reverse
(
request
.
Attachments
)
slices
.
Reverse
(
request
.
Attachments
)
// Update
resource
s' memo_id in the request.
// Update
attachment
s' memo_id in the request.
for
index
,
resource
:=
range
request
.
Attachments
{
for
index
,
attachment
:=
range
request
.
Attachments
{
resourceUID
,
err
:=
ExtractAttachmentUIDFromName
(
resource
.
Name
)
attachmentUID
,
err
:=
ExtractAttachmentUIDFromName
(
attachment
.
Name
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid attachment name: %v"
,
err
)
}
}
temp
Resource
,
err
:=
s
.
Store
.
GetResource
(
ctx
,
&
store
.
FindResource
{
UID
:
&
resource
UID
})
temp
Attachment
,
err
:=
s
.
Store
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
UID
:
&
attachment
UID
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get
attachment
: %v"
,
err
)
}
}
updatedTs
:=
time
.
Now
()
.
Unix
()
+
int64
(
index
)
updatedTs
:=
time
.
Now
()
.
Unix
()
+
int64
(
index
)
if
err
:=
s
.
Store
.
Update
Resource
(
ctx
,
&
store
.
UpdateResource
{
if
err
:=
s
.
Store
.
Update
Attachment
(
ctx
,
&
store
.
UpdateAttachment
{
ID
:
temp
Resource
.
ID
,
ID
:
temp
Attachment
.
ID
,
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
.
ID
,
UpdatedTs
:
&
updatedTs
,
UpdatedTs
:
&
updatedTs
,
});
err
!=
nil
{
});
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to update
resource
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to update
attachment
: %v"
,
err
)
}
}
}
}
...
@@ -85,18 +85,18 @@ func (s *APIV1Service) ListMemoAttachments(ctx context.Context, request *v1pb.Li
...
@@ -85,18 +85,18 @@ func (s *APIV1Service) ListMemoAttachments(ctx context.Context, request *v1pb.Li
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get memo: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get memo: %v"
,
err
)
}
}
resources
,
err
:=
s
.
Store
.
ListResources
(
ctx
,
&
store
.
FindResource
{
attachments
,
err
:=
s
.
Store
.
ListAttachments
(
ctx
,
&
store
.
FindAttachment
{
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
.
ID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
resource
s: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
attachment
s: %v"
,
err
)
}
}
response
:=
&
v1pb
.
ListMemoAttachmentsResponse
{
response
:=
&
v1pb
.
ListMemoAttachmentsResponse
{
Attachments
:
[]
*
v1pb
.
Attachment
{},
Attachments
:
[]
*
v1pb
.
Attachment
{},
}
}
for
_
,
resource
:=
range
resource
s
{
for
_
,
attachment
:=
range
attachment
s
{
response
.
Attachments
=
append
(
response
.
Attachments
,
s
.
convertAttachmentFromStore
(
ctx
,
resource
))
response
.
Attachments
=
append
(
response
.
Attachments
,
s
.
convertAttachmentFromStore
(
ctx
,
attachment
))
}
}
return
response
,
nil
return
response
,
nil
}
}
server/router/api/v1/memo_service.go
View file @
a4920d46
...
@@ -399,14 +399,14 @@ func (s *APIV1Service) DeleteMemo(ctx context.Context, request *v1pb.DeleteMemoR
...
@@ -399,14 +399,14 @@ func (s *APIV1Service) DeleteMemo(ctx context.Context, request *v1pb.DeleteMemoR
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete memo relations"
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete memo relations"
)
}
}
// Delete related
resource
s.
// Delete related
attachment
s.
resources
,
err
:=
s
.
Store
.
ListResources
(
ctx
,
&
store
.
FindResource
{
MemoID
:
&
memo
.
ID
})
attachments
,
err
:=
s
.
Store
.
ListAttachments
(
ctx
,
&
store
.
FindAttachment
{
MemoID
:
&
memo
.
ID
})
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
resource
s"
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list
attachment
s"
)
}
}
for
_
,
resource
:=
range
resource
s
{
for
_
,
attachment
:=
range
attachment
s
{
if
err
:=
s
.
Store
.
Delete
Resource
(
ctx
,
&
store
.
DeleteResource
{
ID
:
resource
.
ID
});
err
!=
nil
{
if
err
:=
s
.
Store
.
Delete
Attachment
(
ctx
,
&
store
.
DeleteAttachment
{
ID
:
attachment
.
ID
});
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete
resource
"
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete
attachment
"
)
}
}
}
}
...
...
server/router/rss/rss.go
View file @
a4920d46
...
@@ -124,22 +124,22 @@ func (s *RSSService) generateRSSFromMemoList(ctx context.Context, memoList []*st
...
@@ -124,22 +124,22 @@ func (s *RSSService) generateRSSFromMemoList(ctx context.Context, memoList []*st
Created
:
time
.
Unix
(
memo
.
CreatedTs
,
0
),
Created
:
time
.
Unix
(
memo
.
CreatedTs
,
0
),
Id
:
link
.
Href
,
Id
:
link
.
Href
,
}
}
resources
,
err
:=
s
.
Store
.
ListResources
(
ctx
,
&
store
.
FindResource
{
attachments
,
err
:=
s
.
Store
.
ListAttachments
(
ctx
,
&
store
.
FindAttachment
{
MemoID
:
&
memo
.
ID
,
MemoID
:
&
memo
.
ID
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
return
""
,
err
return
""
,
err
}
}
if
len
(
resource
s
)
>
0
{
if
len
(
attachment
s
)
>
0
{
resource
:=
resource
s
[
0
]
attachment
:=
attachment
s
[
0
]
enclosure
:=
feeds
.
Enclosure
{}
enclosure
:=
feeds
.
Enclosure
{}
if
resource
.
StorageType
==
storepb
.
ResourceStorageType_EXTERNAL
||
resource
.
StorageType
==
storepb
.
Resource
StorageType_S3
{
if
attachment
.
StorageType
==
storepb
.
AttachmentStorageType_EXTERNAL
||
attachment
.
StorageType
==
storepb
.
Attachment
StorageType_S3
{
enclosure
.
Url
=
resource
.
Reference
enclosure
.
Url
=
attachment
.
Reference
}
else
{
}
else
{
enclosure
.
Url
=
fmt
.
Sprintf
(
"%s/file/attachments/%s/%s"
,
baseURL
,
resource
.
UID
,
resource
.
Filename
)
enclosure
.
Url
=
fmt
.
Sprintf
(
"%s/file/attachments/%s/%s"
,
baseURL
,
attachment
.
UID
,
attachment
.
Filename
)
}
}
enclosure
.
Length
=
strconv
.
Itoa
(
int
(
resource
.
Size
))
enclosure
.
Length
=
strconv
.
Itoa
(
int
(
attachment
.
Size
))
enclosure
.
Type
=
resource
.
Type
enclosure
.
Type
=
attachment
.
Type
feed
.
Items
[
i
]
.
Enclosure
=
&
enclosure
feed
.
Items
[
i
]
.
Enclosure
=
&
enclosure
}
}
}
}
...
...
server/runner/s3presign/runner.go
View file @
a4920d46
...
@@ -49,33 +49,33 @@ func (r *Runner) CheckAndPresign(ctx context.Context) {
...
@@ -49,33 +49,33 @@ func (r *Runner) CheckAndPresign(ctx context.Context) {
return
return
}
}
s3StorageType
:=
storepb
.
Resource
StorageType_S3
s3StorageType
:=
storepb
.
Attachment
StorageType_S3
// Limit
resource
s to a reasonable batch size
// Limit
attachment
s to a reasonable batch size
const
batchSize
=
100
const
batchSize
=
100
offset
:=
0
offset
:=
0
for
{
for
{
limit
:=
batchSize
limit
:=
batchSize
resources
,
err
:=
r
.
Store
.
ListResources
(
ctx
,
&
store
.
FindResource
{
attachments
,
err
:=
r
.
Store
.
ListAttachments
(
ctx
,
&
store
.
FindAttachment
{
GetBlob
:
false
,
GetBlob
:
false
,
StorageType
:
&
s3StorageType
,
StorageType
:
&
s3StorageType
,
Limit
:
&
limit
,
Limit
:
&
limit
,
Offset
:
&
offset
,
Offset
:
&
offset
,
})
})
if
err
!=
nil
{
if
err
!=
nil
{
slog
.
Error
(
"Failed to list
resource
s for presigning"
,
"error"
,
err
)
slog
.
Error
(
"Failed to list
attachment
s for presigning"
,
"error"
,
err
)
return
return
}
}
// Break if no more
resource
s
// Break if no more
attachment
s
if
len
(
resource
s
)
==
0
{
if
len
(
attachment
s
)
==
0
{
break
break
}
}
// Process batch of
resource
s
// Process batch of
attachment
s
presignCount
:=
0
presignCount
:=
0
for
_
,
resource
:=
range
resource
s
{
for
_
,
attachment
:=
range
attachment
s
{
s3ObjectPayload
:=
resource
.
Payload
.
GetS3Object
()
s3ObjectPayload
:=
attachment
.
Payload
.
GetS3Object
()
if
s3ObjectPayload
==
nil
{
if
s3ObjectPayload
==
nil
{
continue
continue
}
}
...
@@ -105,30 +105,30 @@ func (r *Runner) CheckAndPresign(ctx context.Context) {
...
@@ -105,30 +105,30 @@ func (r *Runner) CheckAndPresign(ctx context.Context) {
presignURL
,
err
:=
s3Client
.
PresignGetObject
(
ctx
,
s3ObjectPayload
.
Key
)
presignURL
,
err
:=
s3Client
.
PresignGetObject
(
ctx
,
s3ObjectPayload
.
Key
)
if
err
!=
nil
{
if
err
!=
nil
{
slog
.
Error
(
"Failed to presign URL"
,
"error"
,
err
,
"
resourceID"
,
resource
.
ID
)
slog
.
Error
(
"Failed to presign URL"
,
"error"
,
err
,
"
attachmentID"
,
attachment
.
ID
)
continue
continue
}
}
s3ObjectPayload
.
S3Config
=
s3Config
s3ObjectPayload
.
S3Config
=
s3Config
s3ObjectPayload
.
LastPresignedTime
=
timestamppb
.
New
(
time
.
Now
())
s3ObjectPayload
.
LastPresignedTime
=
timestamppb
.
New
(
time
.
Now
())
if
err
:=
r
.
Store
.
Update
Resource
(
ctx
,
&
store
.
UpdateResource
{
if
err
:=
r
.
Store
.
Update
Attachment
(
ctx
,
&
store
.
UpdateAttachment
{
ID
:
resource
.
ID
,
ID
:
attachment
.
ID
,
Reference
:
&
presignURL
,
Reference
:
&
presignURL
,
Payload
:
&
storepb
.
Resource
Payload
{
Payload
:
&
storepb
.
Attachment
Payload
{
Payload
:
&
storepb
.
Resource
Payload_S3Object_
{
Payload
:
&
storepb
.
Attachment
Payload_S3Object_
{
S3Object
:
s3ObjectPayload
,
S3Object
:
s3ObjectPayload
,
},
},
},
},
});
err
!=
nil
{
});
err
!=
nil
{
slog
.
Error
(
"Failed to update
resource"
,
"error"
,
err
,
"resourceID"
,
resource
.
ID
)
slog
.
Error
(
"Failed to update
attachment"
,
"error"
,
err
,
"attachmentID"
,
attachment
.
ID
)
continue
continue
}
}
presignCount
++
presignCount
++
}
}
slog
.
Info
(
"Presigned batch of S3
resources"
,
"batchSize"
,
len
(
resource
s
),
"presigned"
,
presignCount
)
slog
.
Info
(
"Presigned batch of S3
attachments"
,
"batchSize"
,
len
(
attachment
s
),
"presigned"
,
presignCount
)
// Move to next batch
// Move to next batch
offset
+=
len
(
resource
s
)
offset
+=
len
(
attachment
s
)
}
}
}
}
store/
resource
.go
→
store/
attachment
.go
View file @
a4920d46
...
@@ -13,10 +13,10 @@ import (
...
@@ -13,10 +13,10 @@ import (
storepb
"github.com/usememos/memos/proto/gen/store"
storepb
"github.com/usememos/memos/proto/gen/store"
)
)
type
Resource
struct
{
type
Attachment
struct
{
// ID is the system generated unique identifier for the
resource
.
// ID is the system generated unique identifier for the
attachment
.
ID
int32
ID
int32
// UID is the user defined unique identifier for the
resource
.
// UID is the user defined unique identifier for the
attachment
.
UID
string
UID
string
// Standard fields
// Standard fields
...
@@ -29,15 +29,15 @@ type Resource struct {
...
@@ -29,15 +29,15 @@ type Resource struct {
Blob
[]
byte
Blob
[]
byte
Type
string
Type
string
Size
int64
Size
int64
StorageType
storepb
.
Resource
StorageType
StorageType
storepb
.
Attachment
StorageType
Reference
string
Reference
string
Payload
*
storepb
.
Resource
Payload
Payload
*
storepb
.
Attachment
Payload
// The related memo ID.
// The related memo ID.
MemoID
*
int32
MemoID
*
int32
}
}
type
Find
Resource
struct
{
type
Find
Attachment
struct
{
GetBlob
bool
GetBlob
bool
ID
*
int32
ID
*
int32
UID
*
string
UID
*
string
...
@@ -46,35 +46,35 @@ type FindResource struct {
...
@@ -46,35 +46,35 @@ type FindResource struct {
FilenameSearch
*
string
FilenameSearch
*
string
MemoID
*
int32
MemoID
*
int32
HasRelatedMemo
bool
HasRelatedMemo
bool
StorageType
*
storepb
.
Resource
StorageType
StorageType
*
storepb
.
Attachment
StorageType
Limit
*
int
Limit
*
int
Offset
*
int
Offset
*
int
}
}
type
Update
Resource
struct
{
type
Update
Attachment
struct
{
ID
int32
ID
int32
UID
*
string
UID
*
string
UpdatedTs
*
int64
UpdatedTs
*
int64
Filename
*
string
Filename
*
string
MemoID
*
int32
MemoID
*
int32
Reference
*
string
Reference
*
string
Payload
*
storepb
.
Resource
Payload
Payload
*
storepb
.
Attachment
Payload
}
}
type
Delete
Resource
struct
{
type
Delete
Attachment
struct
{
ID
int32
ID
int32
MemoID
*
int32
MemoID
*
int32
}
}
func
(
s
*
Store
)
Create
Resource
(
ctx
context
.
Context
,
create
*
Resource
)
(
*
Resource
,
error
)
{
func
(
s
*
Store
)
Create
Attachment
(
ctx
context
.
Context
,
create
*
Attachment
)
(
*
Attachment
,
error
)
{
if
!
base
.
UIDMatcher
.
MatchString
(
create
.
UID
)
{
if
!
base
.
UIDMatcher
.
MatchString
(
create
.
UID
)
{
return
nil
,
errors
.
New
(
"invalid uid"
)
return
nil
,
errors
.
New
(
"invalid uid"
)
}
}
return
s
.
driver
.
Create
Resource
(
ctx
,
create
)
return
s
.
driver
.
Create
Attachment
(
ctx
,
create
)
}
}
func
(
s
*
Store
)
List
Resources
(
ctx
context
.
Context
,
find
*
FindResource
)
([]
*
Resource
,
error
)
{
func
(
s
*
Store
)
List
Attachments
(
ctx
context
.
Context
,
find
*
FindAttachment
)
([]
*
Attachment
,
error
)
{
// Set default limits to prevent loading too many
resource
s at once
// Set default limits to prevent loading too many
attachment
s at once
if
find
.
Limit
==
nil
&&
find
.
GetBlob
{
if
find
.
Limit
==
nil
&&
find
.
GetBlob
{
// When fetching blobs, we should be especially careful with limits
// When fetching blobs, we should be especially careful with limits
defaultLimit
:=
10
defaultLimit
:=
10
...
@@ -85,41 +85,41 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou
...
@@ -85,41 +85,41 @@ func (s *Store) ListResources(ctx context.Context, find *FindResource) ([]*Resou
find
.
Limit
=
&
defaultLimit
find
.
Limit
=
&
defaultLimit
}
}
return
s
.
driver
.
List
Resource
s
(
ctx
,
find
)
return
s
.
driver
.
List
Attachment
s
(
ctx
,
find
)
}
}
func
(
s
*
Store
)
Get
Resource
(
ctx
context
.
Context
,
find
*
FindResource
)
(
*
Resource
,
error
)
{
func
(
s
*
Store
)
Get
Attachment
(
ctx
context
.
Context
,
find
*
FindAttachment
)
(
*
Attachment
,
error
)
{
resources
,
err
:=
s
.
ListResource
s
(
ctx
,
find
)
attachments
,
err
:=
s
.
ListAttachment
s
(
ctx
,
find
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
if
len
(
resource
s
)
==
0
{
if
len
(
attachment
s
)
==
0
{
return
nil
,
nil
return
nil
,
nil
}
}
return
resource
s
[
0
],
nil
return
attachment
s
[
0
],
nil
}
}
func
(
s
*
Store
)
Update
Resource
(
ctx
context
.
Context
,
update
*
UpdateResource
)
error
{
func
(
s
*
Store
)
Update
Attachment
(
ctx
context
.
Context
,
update
*
UpdateAttachment
)
error
{
if
update
.
UID
!=
nil
&&
!
base
.
UIDMatcher
.
MatchString
(
*
update
.
UID
)
{
if
update
.
UID
!=
nil
&&
!
base
.
UIDMatcher
.
MatchString
(
*
update
.
UID
)
{
return
errors
.
New
(
"invalid uid"
)
return
errors
.
New
(
"invalid uid"
)
}
}
return
s
.
driver
.
Update
Resource
(
ctx
,
update
)
return
s
.
driver
.
Update
Attachment
(
ctx
,
update
)
}
}
func
(
s
*
Store
)
Delete
Resource
(
ctx
context
.
Context
,
delete
*
DeleteResource
)
error
{
func
(
s
*
Store
)
Delete
Attachment
(
ctx
context
.
Context
,
delete
*
DeleteAttachment
)
error
{
resource
,
err
:=
s
.
GetResource
(
ctx
,
&
FindResource
{
ID
:
&
delete
.
ID
})
attachment
,
err
:=
s
.
GetAttachment
(
ctx
,
&
FindAttachment
{
ID
:
&
delete
.
ID
})
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to get
resource
"
)
return
errors
.
Wrap
(
err
,
"failed to get
attachment
"
)
}
}
if
resource
==
nil
{
if
attachment
==
nil
{
return
errors
.
New
(
"
resource
not found"
)
return
errors
.
New
(
"
attachment
not found"
)
}
}
if
resource
.
StorageType
==
storepb
.
Resource
StorageType_LOCAL
{
if
attachment
.
StorageType
==
storepb
.
Attachment
StorageType_LOCAL
{
if
err
:=
func
()
error
{
if
err
:=
func
()
error
{
p
:=
filepath
.
FromSlash
(
resource
.
Reference
)
p
:=
filepath
.
FromSlash
(
attachment
.
Reference
)
if
!
filepath
.
IsAbs
(
p
)
{
if
!
filepath
.
IsAbs
(
p
)
{
p
=
filepath
.
Join
(
s
.
profile
.
Data
,
p
)
p
=
filepath
.
Join
(
s
.
profile
.
Data
,
p
)
}
}
...
@@ -131,9 +131,9 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro
...
@@ -131,9 +131,9 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro
}();
err
!=
nil
{
}();
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to delete local file"
)
return
errors
.
Wrap
(
err
,
"failed to delete local file"
)
}
}
}
else
if
resource
.
StorageType
==
storepb
.
Resource
StorageType_S3
{
}
else
if
attachment
.
StorageType
==
storepb
.
Attachment
StorageType_S3
{
if
err
:=
func
()
error
{
if
err
:=
func
()
error
{
s3ObjectPayload
:=
resource
.
Payload
.
GetS3Object
()
s3ObjectPayload
:=
attachment
.
Payload
.
GetS3Object
()
if
s3ObjectPayload
==
nil
{
if
s3ObjectPayload
==
nil
{
return
errors
.
Errorf
(
"No s3 object found"
)
return
errors
.
Errorf
(
"No s3 object found"
)
}
}
...
@@ -162,5 +162,5 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro
...
@@ -162,5 +162,5 @@ func (s *Store) DeleteResource(ctx context.Context, delete *DeleteResource) erro
}
}
}
}
return
s
.
driver
.
Delete
Resource
(
ctx
,
delete
)
return
s
.
driver
.
Delete
Attachment
(
ctx
,
delete
)
}
}
store/db/mysql/
resource
.go
→
store/db/mysql/
attachment
.go
View file @
a4920d46
...
@@ -13,18 +13,18 @@ import (
...
@@ -13,18 +13,18 @@ import (
"github.com/usememos/memos/store"
"github.com/usememos/memos/store"
)
)
func
(
d
*
DB
)
Create
Resource
(
ctx
context
.
Context
,
create
*
store
.
Resource
)
(
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
Create
Attachment
(
ctx
context
.
Context
,
create
*
store
.
Attachment
)
(
*
store
.
Attachment
,
error
)
{
fields
:=
[]
string
{
"`uid`"
,
"`filename`"
,
"`blob`"
,
"`type`"
,
"`size`"
,
"`creator_id`"
,
"`memo_id`"
,
"`storage_type`"
,
"`reference`"
,
"`payload`"
}
fields
:=
[]
string
{
"`uid`"
,
"`filename`"
,
"`blob`"
,
"`type`"
,
"`size`"
,
"`creator_id`"
,
"`memo_id`"
,
"`storage_type`"
,
"`reference`"
,
"`payload`"
}
placeholder
:=
[]
string
{
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
}
placeholder
:=
[]
string
{
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
}
storageType
:=
""
storageType
:=
""
if
create
.
StorageType
!=
storepb
.
ResourceStorageType_RESOURCE
_STORAGE_TYPE_UNSPECIFIED
{
if
create
.
StorageType
!=
storepb
.
AttachmentStorageType_ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED
{
storageType
=
create
.
StorageType
.
String
()
storageType
=
create
.
StorageType
.
String
()
}
}
payloadString
:=
"{}"
payloadString
:=
"{}"
if
create
.
Payload
!=
nil
{
if
create
.
Payload
!=
nil
{
bytes
,
err
:=
protojson
.
Marshal
(
create
.
Payload
)
bytes
,
err
:=
protojson
.
Marshal
(
create
.
Payload
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal
resource
payload"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal
attachment
payload"
)
}
}
payloadString
=
string
(
bytes
)
payloadString
=
string
(
bytes
)
}
}
...
@@ -42,10 +42,10 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
...
@@ -42,10 +42,10 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
}
}
id32
:=
int32
(
id
)
id32
:=
int32
(
id
)
return
d
.
Get
Resource
(
ctx
,
&
store
.
FindResource
{
ID
:
&
id32
})
return
d
.
Get
Attachment
(
ctx
,
&
store
.
FindAttachment
{
ID
:
&
id32
})
}
}
func
(
d
*
DB
)
List
Resources
(
ctx
context
.
Context
,
find
*
store
.
FindResource
)
([]
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
List
Attachments
(
ctx
context
.
Context
,
find
*
store
.
FindAttachment
)
([]
*
store
.
Attachment
,
error
)
{
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
if
v
:=
find
.
ID
;
v
!=
nil
{
if
v
:=
find
.
ID
;
v
!=
nil
{
...
@@ -92,43 +92,43 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
...
@@ -92,43 +92,43 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
}
}
defer
rows
.
Close
()
defer
rows
.
Close
()
list
:=
make
([]
*
store
.
Resource
,
0
)
list
:=
make
([]
*
store
.
Attachment
,
0
)
for
rows
.
Next
()
{
for
rows
.
Next
()
{
resource
:=
store
.
Resource
{}
attachment
:=
store
.
Attachment
{}
var
memoID
sql
.
NullInt32
var
memoID
sql
.
NullInt32
var
storageType
string
var
storageType
string
var
payloadBytes
[]
byte
var
payloadBytes
[]
byte
dests
:=
[]
any
{
dests
:=
[]
any
{
&
resource
.
ID
,
&
attachment
.
ID
,
&
resource
.
UID
,
&
attachment
.
UID
,
&
resource
.
Filename
,
&
attachment
.
Filename
,
&
resource
.
Type
,
&
attachment
.
Type
,
&
resource
.
Size
,
&
attachment
.
Size
,
&
resource
.
CreatorID
,
&
attachment
.
CreatorID
,
&
resource
.
CreatedTs
,
&
attachment
.
CreatedTs
,
&
resource
.
UpdatedTs
,
&
attachment
.
UpdatedTs
,
&
memoID
,
&
memoID
,
&
storageType
,
&
storageType
,
&
resource
.
Reference
,
&
attachment
.
Reference
,
&
payloadBytes
,
&
payloadBytes
,
}
}
if
find
.
GetBlob
{
if
find
.
GetBlob
{
dests
=
append
(
dests
,
&
resource
.
Blob
)
dests
=
append
(
dests
,
&
attachment
.
Blob
)
}
}
if
err
:=
rows
.
Scan
(
dests
...
);
err
!=
nil
{
if
err
:=
rows
.
Scan
(
dests
...
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
if
memoID
.
Valid
{
if
memoID
.
Valid
{
resource
.
MemoID
=
&
memoID
.
Int32
attachment
.
MemoID
=
&
memoID
.
Int32
}
}
resource
.
StorageType
=
storepb
.
ResourceStorageType
(
storepb
.
Resource
StorageType_value
[
storageType
])
attachment
.
StorageType
=
storepb
.
AttachmentStorageType
(
storepb
.
Attachment
StorageType_value
[
storageType
])
payload
:=
&
storepb
.
Resource
Payload
{}
payload
:=
&
storepb
.
Attachment
Payload
{}
if
err
:=
protojsonUnmarshaler
.
Unmarshal
(
payloadBytes
,
payload
);
err
!=
nil
{
if
err
:=
protojsonUnmarshaler
.
Unmarshal
(
payloadBytes
,
payload
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
resource
.
Payload
=
payload
attachment
.
Payload
=
payload
list
=
append
(
list
,
&
resource
)
list
=
append
(
list
,
&
attachment
)
}
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
if
err
:=
rows
.
Err
();
err
!=
nil
{
...
@@ -138,8 +138,8 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
...
@@ -138,8 +138,8 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
return
list
,
nil
return
list
,
nil
}
}
func
(
d
*
DB
)
Get
Resource
(
ctx
context
.
Context
,
find
*
store
.
FindResource
)
(
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
Get
Attachment
(
ctx
context
.
Context
,
find
*
store
.
FindAttachment
)
(
*
store
.
Attachment
,
error
)
{
list
,
err
:=
d
.
List
Resource
s
(
ctx
,
find
)
list
,
err
:=
d
.
List
Attachment
s
(
ctx
,
find
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
@@ -150,7 +150,7 @@ func (d *DB) GetResource(ctx context.Context, find *store.FindResource) (*store.
...
@@ -150,7 +150,7 @@ func (d *DB) GetResource(ctx context.Context, find *store.FindResource) (*store.
return
list
[
0
],
nil
return
list
[
0
],
nil
}
}
func
(
d
*
DB
)
Update
Resource
(
ctx
context
.
Context
,
update
*
store
.
UpdateResource
)
error
{
func
(
d
*
DB
)
Update
Attachment
(
ctx
context
.
Context
,
update
*
store
.
UpdateAttachment
)
error
{
set
,
args
:=
[]
string
{},
[]
any
{}
set
,
args
:=
[]
string
{},
[]
any
{}
if
v
:=
update
.
UID
;
v
!=
nil
{
if
v
:=
update
.
UID
;
v
!=
nil
{
...
@@ -171,7 +171,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -171,7 +171,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
if
v
:=
update
.
Payload
;
v
!=
nil
{
if
v
:=
update
.
Payload
;
v
!=
nil
{
bytes
,
err
:=
protojson
.
Marshal
(
v
)
bytes
,
err
:=
protojson
.
Marshal
(
v
)
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to marshal
resource
payload"
)
return
errors
.
Wrap
(
err
,
"failed to marshal
attachment
payload"
)
}
}
set
,
args
=
append
(
set
,
"`payload` = ?"
),
append
(
args
,
string
(
bytes
))
set
,
args
=
append
(
set
,
"`payload` = ?"
),
append
(
args
,
string
(
bytes
))
}
}
...
@@ -188,7 +188,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -188,7 +188,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
return
nil
return
nil
}
}
func
(
d
*
DB
)
Delete
Resource
(
ctx
context
.
Context
,
delete
*
store
.
DeleteResource
)
error
{
func
(
d
*
DB
)
Delete
Attachment
(
ctx
context
.
Context
,
delete
*
store
.
DeleteAttachment
)
error
{
stmt
:=
"DELETE FROM `resource` WHERE `id` = ?"
stmt
:=
"DELETE FROM `resource` WHERE `id` = ?"
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
delete
.
ID
)
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
delete
.
ID
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
store/db/postgres/
resource
.go
→
store/db/postgres/
attachment
.go
View file @
a4920d46
...
@@ -13,17 +13,17 @@ import (
...
@@ -13,17 +13,17 @@ import (
"github.com/usememos/memos/store"
"github.com/usememos/memos/store"
)
)
func
(
d
*
DB
)
Create
Resource
(
ctx
context
.
Context
,
create
*
store
.
Resource
)
(
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
Create
Attachment
(
ctx
context
.
Context
,
create
*
store
.
Attachment
)
(
*
store
.
Attachment
,
error
)
{
fields
:=
[]
string
{
"uid"
,
"filename"
,
"blob"
,
"type"
,
"size"
,
"creator_id"
,
"memo_id"
,
"storage_type"
,
"reference"
,
"payload"
}
fields
:=
[]
string
{
"uid"
,
"filename"
,
"blob"
,
"type"
,
"size"
,
"creator_id"
,
"memo_id"
,
"storage_type"
,
"reference"
,
"payload"
}
storageType
:=
""
storageType
:=
""
if
create
.
StorageType
!=
storepb
.
ResourceStorageType_RESOURCE
_STORAGE_TYPE_UNSPECIFIED
{
if
create
.
StorageType
!=
storepb
.
AttachmentStorageType_ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED
{
storageType
=
create
.
StorageType
.
String
()
storageType
=
create
.
StorageType
.
String
()
}
}
payloadString
:=
"{}"
payloadString
:=
"{}"
if
create
.
Payload
!=
nil
{
if
create
.
Payload
!=
nil
{
bytes
,
err
:=
protojson
.
Marshal
(
create
.
Payload
)
bytes
,
err
:=
protojson
.
Marshal
(
create
.
Payload
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal
resource
payload"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal
attachment
payload"
)
}
}
payloadString
=
string
(
bytes
)
payloadString
=
string
(
bytes
)
}
}
...
@@ -36,7 +36,7 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
...
@@ -36,7 +36,7 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
return
create
,
nil
return
create
,
nil
}
}
func
(
d
*
DB
)
List
Resources
(
ctx
context
.
Context
,
find
*
store
.
FindResource
)
([]
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
List
Attachments
(
ctx
context
.
Context
,
find
*
store
.
FindAttachment
)
([]
*
store
.
Attachment
,
error
)
{
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
if
v
:=
find
.
ID
;
v
!=
nil
{
if
v
:=
find
.
ID
;
v
!=
nil
{
...
@@ -89,43 +89,43 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
...
@@ -89,43 +89,43 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
}
}
defer
rows
.
Close
()
defer
rows
.
Close
()
list
:=
make
([]
*
store
.
Resource
,
0
)
list
:=
make
([]
*
store
.
Attachment
,
0
)
for
rows
.
Next
()
{
for
rows
.
Next
()
{
resource
:=
store
.
Resource
{}
attachment
:=
store
.
Attachment
{}
var
memoID
sql
.
NullInt32
var
memoID
sql
.
NullInt32
var
storageType
string
var
storageType
string
var
payloadBytes
[]
byte
var
payloadBytes
[]
byte
dests
:=
[]
any
{
dests
:=
[]
any
{
&
resource
.
ID
,
&
attachment
.
ID
,
&
resource
.
UID
,
&
attachment
.
UID
,
&
resource
.
Filename
,
&
attachment
.
Filename
,
&
resource
.
Type
,
&
attachment
.
Type
,
&
resource
.
Size
,
&
attachment
.
Size
,
&
resource
.
CreatorID
,
&
attachment
.
CreatorID
,
&
resource
.
CreatedTs
,
&
attachment
.
CreatedTs
,
&
resource
.
UpdatedTs
,
&
attachment
.
UpdatedTs
,
&
memoID
,
&
memoID
,
&
storageType
,
&
storageType
,
&
resource
.
Reference
,
&
attachment
.
Reference
,
&
payloadBytes
,
&
payloadBytes
,
}
}
if
find
.
GetBlob
{
if
find
.
GetBlob
{
dests
=
append
(
dests
,
&
resource
.
Blob
)
dests
=
append
(
dests
,
&
attachment
.
Blob
)
}
}
if
err
:=
rows
.
Scan
(
dests
...
);
err
!=
nil
{
if
err
:=
rows
.
Scan
(
dests
...
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
if
memoID
.
Valid
{
if
memoID
.
Valid
{
resource
.
MemoID
=
&
memoID
.
Int32
attachment
.
MemoID
=
&
memoID
.
Int32
}
}
resource
.
StorageType
=
storepb
.
ResourceStorageType
(
storepb
.
Resource
StorageType_value
[
storageType
])
attachment
.
StorageType
=
storepb
.
AttachmentStorageType
(
storepb
.
Attachment
StorageType_value
[
storageType
])
payload
:=
&
storepb
.
Resource
Payload
{}
payload
:=
&
storepb
.
Attachment
Payload
{}
if
err
:=
protojsonUnmarshaler
.
Unmarshal
(
payloadBytes
,
payload
);
err
!=
nil
{
if
err
:=
protojsonUnmarshaler
.
Unmarshal
(
payloadBytes
,
payload
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
resource
.
Payload
=
payload
attachment
.
Payload
=
payload
list
=
append
(
list
,
&
resource
)
list
=
append
(
list
,
&
attachment
)
}
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
if
err
:=
rows
.
Err
();
err
!=
nil
{
...
@@ -135,7 +135,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
...
@@ -135,7 +135,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
return
list
,
nil
return
list
,
nil
}
}
func
(
d
*
DB
)
Update
Resource
(
ctx
context
.
Context
,
update
*
store
.
UpdateResource
)
error
{
func
(
d
*
DB
)
Update
Attachment
(
ctx
context
.
Context
,
update
*
store
.
UpdateAttachment
)
error
{
set
,
args
:=
[]
string
{},
[]
any
{}
set
,
args
:=
[]
string
{},
[]
any
{}
if
v
:=
update
.
UID
;
v
!=
nil
{
if
v
:=
update
.
UID
;
v
!=
nil
{
...
@@ -156,7 +156,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -156,7 +156,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
if
v
:=
update
.
Payload
;
v
!=
nil
{
if
v
:=
update
.
Payload
;
v
!=
nil
{
bytes
,
err
:=
protojson
.
Marshal
(
v
)
bytes
,
err
:=
protojson
.
Marshal
(
v
)
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to marshal
resource
payload"
)
return
errors
.
Wrap
(
err
,
"failed to marshal
attachment
payload"
)
}
}
set
,
args
=
append
(
set
,
"payload = "
+
placeholder
(
len
(
args
)
+
1
)),
append
(
args
,
string
(
bytes
))
set
,
args
=
append
(
set
,
"payload = "
+
placeholder
(
len
(
args
)
+
1
)),
append
(
args
,
string
(
bytes
))
}
}
...
@@ -173,7 +173,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -173,7 +173,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
return
nil
return
nil
}
}
func
(
d
*
DB
)
Delete
Resource
(
ctx
context
.
Context
,
delete
*
store
.
DeleteResource
)
error
{
func
(
d
*
DB
)
Delete
Attachment
(
ctx
context
.
Context
,
delete
*
store
.
DeleteAttachment
)
error
{
stmt
:=
`DELETE FROM resource WHERE id = $1`
stmt
:=
`DELETE FROM resource WHERE id = $1`
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
delete
.
ID
)
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
delete
.
ID
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
store/db/sqlite/
resource
.go
→
store/db/sqlite/
attachment
.go
View file @
a4920d46
...
@@ -13,18 +13,18 @@ import (
...
@@ -13,18 +13,18 @@ import (
"github.com/usememos/memos/store"
"github.com/usememos/memos/store"
)
)
func
(
d
*
DB
)
Create
Resource
(
ctx
context
.
Context
,
create
*
store
.
Resource
)
(
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
Create
Attachment
(
ctx
context
.
Context
,
create
*
store
.
Attachment
)
(
*
store
.
Attachment
,
error
)
{
fields
:=
[]
string
{
"`uid`"
,
"`filename`"
,
"`blob`"
,
"`type`"
,
"`size`"
,
"`creator_id`"
,
"`memo_id`"
,
"`storage_type`"
,
"`reference`"
,
"`payload`"
}
fields
:=
[]
string
{
"`uid`"
,
"`filename`"
,
"`blob`"
,
"`type`"
,
"`size`"
,
"`creator_id`"
,
"`memo_id`"
,
"`storage_type`"
,
"`reference`"
,
"`payload`"
}
placeholder
:=
[]
string
{
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
}
placeholder
:=
[]
string
{
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
,
"?"
}
storageType
:=
""
storageType
:=
""
if
create
.
StorageType
!=
storepb
.
ResourceStorageType_RESOURCE
_STORAGE_TYPE_UNSPECIFIED
{
if
create
.
StorageType
!=
storepb
.
AttachmentStorageType_ATTACHMENT
_STORAGE_TYPE_UNSPECIFIED
{
storageType
=
create
.
StorageType
.
String
()
storageType
=
create
.
StorageType
.
String
()
}
}
payloadString
:=
"{}"
payloadString
:=
"{}"
if
create
.
Payload
!=
nil
{
if
create
.
Payload
!=
nil
{
bytes
,
err
:=
protojson
.
Marshal
(
create
.
Payload
)
bytes
,
err
:=
protojson
.
Marshal
(
create
.
Payload
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal
resource
payload"
)
return
nil
,
errors
.
Wrap
(
err
,
"failed to marshal
attachment
payload"
)
}
}
payloadString
=
string
(
bytes
)
payloadString
=
string
(
bytes
)
}
}
...
@@ -38,7 +38,7 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
...
@@ -38,7 +38,7 @@ func (d *DB) CreateResource(ctx context.Context, create *store.Resource) (*store
return
create
,
nil
return
create
,
nil
}
}
func
(
d
*
DB
)
List
Resources
(
ctx
context
.
Context
,
find
*
store
.
FindResource
)
([]
*
store
.
Resource
,
error
)
{
func
(
d
*
DB
)
List
Attachments
(
ctx
context
.
Context
,
find
*
store
.
FindAttachment
)
([]
*
store
.
Attachment
,
error
)
{
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
where
,
args
:=
[]
string
{
"1 = 1"
},
[]
any
{}
if
v
:=
find
.
ID
;
v
!=
nil
{
if
v
:=
find
.
ID
;
v
!=
nil
{
...
@@ -85,43 +85,43 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
...
@@ -85,43 +85,43 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
}
}
defer
rows
.
Close
()
defer
rows
.
Close
()
list
:=
make
([]
*
store
.
Resource
,
0
)
list
:=
make
([]
*
store
.
Attachment
,
0
)
for
rows
.
Next
()
{
for
rows
.
Next
()
{
resource
:=
store
.
Resource
{}
attachment
:=
store
.
Attachment
{}
var
memoID
sql
.
NullInt32
var
memoID
sql
.
NullInt32
var
storageType
string
var
storageType
string
var
payloadBytes
[]
byte
var
payloadBytes
[]
byte
dests
:=
[]
any
{
dests
:=
[]
any
{
&
resource
.
ID
,
&
attachment
.
ID
,
&
resource
.
UID
,
&
attachment
.
UID
,
&
resource
.
Filename
,
&
attachment
.
Filename
,
&
resource
.
Type
,
&
attachment
.
Type
,
&
resource
.
Size
,
&
attachment
.
Size
,
&
resource
.
CreatorID
,
&
attachment
.
CreatorID
,
&
resource
.
CreatedTs
,
&
attachment
.
CreatedTs
,
&
resource
.
UpdatedTs
,
&
attachment
.
UpdatedTs
,
&
memoID
,
&
memoID
,
&
storageType
,
&
storageType
,
&
resource
.
Reference
,
&
attachment
.
Reference
,
&
payloadBytes
,
&
payloadBytes
,
}
}
if
find
.
GetBlob
{
if
find
.
GetBlob
{
dests
=
append
(
dests
,
&
resource
.
Blob
)
dests
=
append
(
dests
,
&
attachment
.
Blob
)
}
}
if
err
:=
rows
.
Scan
(
dests
...
);
err
!=
nil
{
if
err
:=
rows
.
Scan
(
dests
...
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
if
memoID
.
Valid
{
if
memoID
.
Valid
{
resource
.
MemoID
=
&
memoID
.
Int32
attachment
.
MemoID
=
&
memoID
.
Int32
}
}
resource
.
StorageType
=
storepb
.
ResourceStorageType
(
storepb
.
Resource
StorageType_value
[
storageType
])
attachment
.
StorageType
=
storepb
.
AttachmentStorageType
(
storepb
.
Attachment
StorageType_value
[
storageType
])
payload
:=
&
storepb
.
Resource
Payload
{}
payload
:=
&
storepb
.
Attachment
Payload
{}
if
err
:=
protojsonUnmarshaler
.
Unmarshal
(
payloadBytes
,
payload
);
err
!=
nil
{
if
err
:=
protojsonUnmarshaler
.
Unmarshal
(
payloadBytes
,
payload
);
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
resource
.
Payload
=
payload
attachment
.
Payload
=
payload
list
=
append
(
list
,
&
resource
)
list
=
append
(
list
,
&
attachment
)
}
}
if
err
:=
rows
.
Err
();
err
!=
nil
{
if
err
:=
rows
.
Err
();
err
!=
nil
{
...
@@ -131,7 +131,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
...
@@ -131,7 +131,7 @@ func (d *DB) ListResources(ctx context.Context, find *store.FindResource) ([]*st
return
list
,
nil
return
list
,
nil
}
}
func
(
d
*
DB
)
Update
Resource
(
ctx
context
.
Context
,
update
*
store
.
UpdateResource
)
error
{
func
(
d
*
DB
)
Update
Attachment
(
ctx
context
.
Context
,
update
*
store
.
UpdateAttachment
)
error
{
set
,
args
:=
[]
string
{},
[]
any
{}
set
,
args
:=
[]
string
{},
[]
any
{}
if
v
:=
update
.
UID
;
v
!=
nil
{
if
v
:=
update
.
UID
;
v
!=
nil
{
...
@@ -152,7 +152,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -152,7 +152,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
if
v
:=
update
.
Payload
;
v
!=
nil
{
if
v
:=
update
.
Payload
;
v
!=
nil
{
bytes
,
err
:=
protojson
.
Marshal
(
v
)
bytes
,
err
:=
protojson
.
Marshal
(
v
)
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to marshal
resource
payload"
)
return
errors
.
Wrap
(
err
,
"failed to marshal
attachment
payload"
)
}
}
set
,
args
=
append
(
set
,
"`payload` = ?"
),
append
(
args
,
string
(
bytes
))
set
,
args
=
append
(
set
,
"`payload` = ?"
),
append
(
args
,
string
(
bytes
))
}
}
...
@@ -161,7 +161,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -161,7 +161,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
stmt
:=
"UPDATE `resource` SET "
+
strings
.
Join
(
set
,
", "
)
+
" WHERE `id` = ?"
stmt
:=
"UPDATE `resource` SET "
+
strings
.
Join
(
set
,
", "
)
+
" WHERE `id` = ?"
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
args
...
)
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
args
...
)
if
err
!=
nil
{
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to update
resource
"
)
return
errors
.
Wrap
(
err
,
"failed to update
attachment
"
)
}
}
if
_
,
err
:=
result
.
RowsAffected
();
err
!=
nil
{
if
_
,
err
:=
result
.
RowsAffected
();
err
!=
nil
{
return
err
return
err
...
@@ -169,7 +169,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
...
@@ -169,7 +169,7 @@ func (d *DB) UpdateResource(ctx context.Context, update *store.UpdateResource) e
return
nil
return
nil
}
}
func
(
d
*
DB
)
Delete
Resource
(
ctx
context
.
Context
,
delete
*
store
.
DeleteResource
)
error
{
func
(
d
*
DB
)
Delete
Attachment
(
ctx
context
.
Context
,
delete
*
store
.
DeleteAttachment
)
error
{
stmt
:=
"DELETE FROM `resource` WHERE `id` = ?"
stmt
:=
"DELETE FROM `resource` WHERE `id` = ?"
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
delete
.
ID
)
result
,
err
:=
d
.
db
.
ExecContext
(
ctx
,
stmt
,
delete
.
ID
)
if
err
!=
nil
{
if
err
!=
nil
{
...
...
store/driver.go
View file @
a4920d46
...
@@ -25,11 +25,11 @@ type Driver interface {
...
@@ -25,11 +25,11 @@ type Driver interface {
CreateActivity
(
ctx
context
.
Context
,
create
*
Activity
)
(
*
Activity
,
error
)
CreateActivity
(
ctx
context
.
Context
,
create
*
Activity
)
(
*
Activity
,
error
)
ListActivities
(
ctx
context
.
Context
,
find
*
FindActivity
)
([]
*
Activity
,
error
)
ListActivities
(
ctx
context
.
Context
,
find
*
FindActivity
)
([]
*
Activity
,
error
)
//
Resource
model related methods.
//
Attachment
model related methods.
Create
Resource
(
ctx
context
.
Context
,
create
*
Resource
)
(
*
Resource
,
error
)
Create
Attachment
(
ctx
context
.
Context
,
create
*
Attachment
)
(
*
Attachment
,
error
)
List
Resources
(
ctx
context
.
Context
,
find
*
FindResource
)
([]
*
Resource
,
error
)
List
Attachments
(
ctx
context
.
Context
,
find
*
FindAttachment
)
([]
*
Attachment
,
error
)
Update
Resource
(
ctx
context
.
Context
,
update
*
UpdateResource
)
error
Update
Attachment
(
ctx
context
.
Context
,
update
*
UpdateAttachment
)
error
Delete
Resource
(
ctx
context
.
Context
,
delete
*
DeleteResource
)
error
Delete
Attachment
(
ctx
context
.
Context
,
delete
*
DeleteAttachment
)
error
// Memo model related methods.
// Memo model related methods.
CreateMemo
(
ctx
context
.
Context
,
create
*
Memo
)
(
*
Memo
,
error
)
CreateMemo
(
ctx
context
.
Context
,
create
*
Memo
)
(
*
Memo
,
error
)
...
...
store/test/
resource
_test.go
→
store/test/
attachment
_test.go
View file @
a4920d46
...
@@ -10,10 +10,10 @@ import (
...
@@ -10,10 +10,10 @@ import (
"github.com/usememos/memos/store"
"github.com/usememos/memos/store"
)
)
func
Test
Resource
Store
(
t
*
testing
.
T
)
{
func
Test
Attachment
Store
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
ctx
:=
context
.
Background
()
ts
:=
NewTestingStore
(
ctx
,
t
)
ts
:=
NewTestingStore
(
ctx
,
t
)
_
,
err
:=
ts
.
Create
Resource
(
ctx
,
&
store
.
Resource
{
_
,
err
:=
ts
.
Create
Attachment
(
ctx
,
&
store
.
Attachment
{
UID
:
shortuuid
.
New
(),
UID
:
shortuuid
.
New
(),
CreatorID
:
101
,
CreatorID
:
101
,
Filename
:
"test.epub"
,
Filename
:
"test.epub"
,
...
@@ -25,39 +25,39 @@ func TestResourceStore(t *testing.T) {
...
@@ -25,39 +25,39 @@ func TestResourceStore(t *testing.T) {
correctFilename
:=
"test.epub"
correctFilename
:=
"test.epub"
incorrectFilename
:=
"test.png"
incorrectFilename
:=
"test.png"
resource
,
err
:=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
attachment
,
err
:=
ts
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
Filename
:
&
correctFilename
,
Filename
:
&
correctFilename
,
})
})
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
correctFilename
,
resource
.
Filename
)
require
.
Equal
(
t
,
correctFilename
,
attachment
.
Filename
)
require
.
Equal
(
t
,
int32
(
1
),
resource
.
ID
)
require
.
Equal
(
t
,
int32
(
1
),
attachment
.
ID
)
notFound
Resource
,
err
:=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
notFound
Attachment
,
err
:=
ts
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
Filename
:
&
incorrectFilename
,
Filename
:
&
incorrectFilename
,
})
})
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
notFound
Resource
)
require
.
Nil
(
t
,
notFound
Attachment
)
var
correctCreatorID
int32
=
101
var
correctCreatorID
int32
=
101
var
incorrectCreatorID
int32
=
102
var
incorrectCreatorID
int32
=
102
_
,
err
=
ts
.
Get
Resource
(
ctx
,
&
store
.
FindResource
{
_
,
err
=
ts
.
Get
Attachment
(
ctx
,
&
store
.
FindAttachment
{
CreatorID
:
&
correctCreatorID
,
CreatorID
:
&
correctCreatorID
,
})
})
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
notFound
Resource
,
err
=
ts
.
GetResource
(
ctx
,
&
store
.
FindResource
{
notFound
Attachment
,
err
=
ts
.
GetAttachment
(
ctx
,
&
store
.
FindAttachment
{
CreatorID
:
&
incorrectCreatorID
,
CreatorID
:
&
incorrectCreatorID
,
})
})
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
require
.
Nil
(
t
,
notFound
Resource
)
require
.
Nil
(
t
,
notFound
Attachment
)
err
=
ts
.
Delete
Resource
(
ctx
,
&
store
.
DeleteResource
{
err
=
ts
.
Delete
Attachment
(
ctx
,
&
store
.
DeleteAttachment
{
ID
:
1
,
ID
:
1
,
})
})
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
err
)
err
=
ts
.
Delete
Resource
(
ctx
,
&
store
.
DeleteResource
{
err
=
ts
.
Delete
Attachment
(
ctx
,
&
store
.
DeleteAttachment
{
ID
:
2
,
ID
:
2
,
})
})
require
.
ErrorContains
(
t
,
err
,
"
resource
not found"
)
require
.
ErrorContains
(
t
,
err
,
"
attachment
not found"
)
ts
.
Close
()
ts
.
Close
()
}
}
web/src/components/
Resource
Icon.tsx
→
web/src/components/
Attachment
Icon.tsx
View file @
a4920d46
...
@@ -17,15 +17,15 @@ import showPreviewImageDialog from "./PreviewImageDialog";
...
@@ -17,15 +17,15 @@ import showPreviewImageDialog from "./PreviewImageDialog";
import
SquareDiv
from
"./kit/SquareDiv"
;
import
SquareDiv
from
"./kit/SquareDiv"
;
interface
Props
{
interface
Props
{
resource
:
Attachment
;
attachment
:
Attachment
;
className
?:
string
;
className
?:
string
;
strokeWidth
?:
number
;
strokeWidth
?:
number
;
}
}
const
Resource
Icon
=
(
props
:
Props
)
=>
{
const
Attachment
Icon
=
(
props
:
Props
)
=>
{
const
{
resource
}
=
props
;
const
{
attachment
}
=
props
;
const
resourceType
=
getAttachmentType
(
resource
);
const
resourceType
=
getAttachmentType
(
attachment
);
const
resourceUrl
=
getAttachmentUrl
(
resource
);
const
resourceUrl
=
getAttachmentUrl
(
attachment
);
const
className
=
cn
(
"w-full h-auto"
,
props
.
className
);
const
className
=
cn
(
"w-full h-auto"
,
props
.
className
);
const
strokeWidth
=
props
.
strokeWidth
;
const
strokeWidth
=
props
.
strokeWidth
;
...
@@ -38,7 +38,7 @@ const ResourceIcon = (props: Props) => {
...
@@ -38,7 +38,7 @@ const ResourceIcon = (props: Props) => {
<
SquareDiv
className=
{
cn
(
className
,
"flex items-center justify-center overflow-clip"
)
}
>
<
SquareDiv
className=
{
cn
(
className
,
"flex items-center justify-center overflow-clip"
)
}
>
<
img
<
img
className=
"min-w-full min-h-full object-cover"
className=
"min-w-full min-h-full object-cover"
src=
{
resource
.
externalLink
?
resourceUrl
:
resourceUrl
+
"?thumbnail=true"
}
src=
{
attachment
.
externalLink
?
resourceUrl
:
resourceUrl
+
"?thumbnail=true"
}
onClick=
{
()
=>
showPreviewImageDialog
(
resourceUrl
)
}
onClick=
{
()
=>
showPreviewImageDialog
(
resourceUrl
)
}
decoding=
"async"
decoding=
"async"
loading=
"lazy"
loading=
"lazy"
...
@@ -47,7 +47,7 @@ const ResourceIcon = (props: Props) => {
...
@@ -47,7 +47,7 @@ const ResourceIcon = (props: Props) => {
);
);
}
}
const
get
Resource
Icon
=
()
=>
{
const
get
Attachment
Icon
=
()
=>
{
switch
(
resourceType
)
{
switch
(
resourceType
)
{
case
"video/*"
:
case
"video/*"
:
return
<
FileVideo2Icon
strokeWidth=
{
strokeWidth
}
className=
"w-full h-auto"
/>;
return
<
FileVideo2Icon
strokeWidth=
{
strokeWidth
}
className=
"w-full h-auto"
/>;
...
@@ -74,9 +74,9 @@ const ResourceIcon = (props: Props) => {
...
@@ -74,9 +74,9 @@ const ResourceIcon = (props: Props) => {
return
(
return
(
<
div
onClick=
{
previewResource
}
className=
{
cn
(
className
,
"max-w-16 opacity-50"
)
}
>
<
div
onClick=
{
previewResource
}
className=
{
cn
(
className
,
"max-w-16 opacity-50"
)
}
>
{
get
Resource
Icon
()
}
{
get
Attachment
Icon
()
}
</
div
>
</
div
>
);
);
};
};
export
default
React
.
memo
(
Resource
Icon
);
export
default
React
.
memo
(
Attachment
Icon
);
web/src/components/MemoAttachment.tsx
0 → 100644
View file @
a4920d46
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
getAttachmentUrl
}
from
"@/utils/attachment"
;
import
AttachmentIcon
from
"./AttachmentIcon"
;
interface
Props
{
attachment
:
Attachment
;
className
?:
string
;
}
const
MemoAttachment
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
className
,
attachment
}
=
props
;
const
attachmentUrl
=
getAttachmentUrl
(
attachment
);
const
handlePreviewBtnClick
=
()
=>
{
window
.
open
(
attachmentUrl
);
};
return
(
<
div
className=
{
`w-auto flex flex-row justify-start items-center text-gray-500 dark:text-gray-400 hover:opacity-80 ${className}`
}
>
{
attachment
.
type
.
startsWith
(
"audio"
)
?
(
<
audio
src=
{
attachmentUrl
}
controls
></
audio
>
)
:
(
<>
<
AttachmentIcon
className=
"w-4! h-4! mr-1"
attachment=
{
attachment
}
/>
<
span
className=
"text-sm max-w-[256px] truncate cursor-pointer"
onClick=
{
handlePreviewBtnClick
}
>
{
attachment
.
filename
}
</
span
>
</>
)
}
</
div
>
);
};
export
default
MemoAttachment
;
web/src/components/MemoAttachmentListView.tsx
View file @
a4920d46
...
@@ -2,7 +2,7 @@ import { memo } from "react";
...
@@ -2,7 +2,7 @@ import { memo } from "react";
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
cn
}
from
"@/utils"
;
import
{
cn
}
from
"@/utils"
;
import
{
getAttachmentType
,
getAttachmentUrl
}
from
"@/utils/attachment"
;
import
{
getAttachmentType
,
getAttachmentUrl
}
from
"@/utils/attachment"
;
import
Memo
Resource
from
"./MemoResource
"
;
import
Memo
Attachment
from
"./MemoAttachment
"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
import
showPreviewImageDialog
from
"./PreviewImageDialog"
;
const
MemoAttachmentListView
=
({
attachments
=
[]
}:
{
attachments
:
Attachment
[]
})
=>
{
const
MemoAttachmentListView
=
({
attachments
=
[]
}:
{
attachments
:
Attachment
[]
})
=>
{
...
@@ -78,7 +78,7 @@ const MemoAttachmentListView = ({ attachments = [] }: { attachments: Attachment[
...
@@ -78,7 +78,7 @@ const MemoAttachmentListView = ({ attachments = [] }: { attachments: Attachment[
return
(
return
(
<
div
className=
"w-full flex flex-row justify-start overflow-auto gap-2"
>
<
div
className=
"w-full flex flex-row justify-start overflow-auto gap-2"
>
{
otherAttachments
.
map
((
attachment
)
=>
(
{
otherAttachments
.
map
((
attachment
)
=>
(
<
Memo
Resource
key=
{
attachment
.
name
}
resource
=
{
attachment
}
/>
<
Memo
Attachment
key=
{
attachment
.
name
}
attachment
=
{
attachment
}
/>
))
}
))
}
</
div
>
</
div
>
);
);
...
...
web/src/components/MemoEditor/ActionButton/UploadAttachmentButton.tsx
0 → 100644
View file @
a4920d46
import
{
Button
}
from
"@usememos/mui"
;
import
{
LoaderIcon
,
PaperclipIcon
}
from
"lucide-react"
;
import
{
observer
}
from
"mobx-react-lite"
;
import
{
useContext
,
useRef
,
useState
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
{
attachmentStore
}
from
"@/store/v2"
;
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
MemoEditorContext
}
from
"../types"
;
interface
Props
{
isUploading
?:
boolean
;
}
interface
State
{
uploadingFlag
:
boolean
;
}
const
UploadAttachmentButton
=
observer
((
props
:
Props
)
=>
{
const
context
=
useContext
(
MemoEditorContext
);
const
[
state
,
setState
]
=
useState
<
State
>
({
uploadingFlag
:
false
,
});
const
fileInputRef
=
useRef
<
HTMLInputElement
>
(
null
);
const
handleFileInputChange
=
async
()
=>
{
if
(
!
fileInputRef
.
current
||
!
fileInputRef
.
current
.
files
||
fileInputRef
.
current
.
files
.
length
===
0
)
{
return
;
}
if
(
state
.
uploadingFlag
)
{
return
;
}
setState
((
state
)
=>
{
return
{
...
state
,
uploadingFlag
:
true
,
};
});
const
createdAttachmentList
:
Attachment
[]
=
[];
try
{
if
(
!
fileInputRef
.
current
||
!
fileInputRef
.
current
.
files
)
{
return
;
}
for
(
const
file
of
fileInputRef
.
current
.
files
)
{
const
{
name
:
filename
,
size
,
type
}
=
file
;
const
buffer
=
new
Uint8Array
(
await
file
.
arrayBuffer
());
const
attachment
=
await
attachmentStore
.
createAttachment
({
attachment
:
Attachment
.
fromPartial
({
filename
,
size
,
type
,
content
:
buffer
,
}),
attachmentId
:
""
,
});
createdAttachmentList
.
push
(
attachment
);
}
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
details
);
}
context
.
setAttachmentList
([...
context
.
attachmentList
,
...
createdAttachmentList
]);
setState
((
state
)
=>
{
return
{
...
state
,
uploadingFlag
:
false
,
};
});
};
const
isUploading
=
state
.
uploadingFlag
||
props
.
isUploading
;
return
(
<
Button
className=
"relative p-0"
variant=
"plain"
disabled=
{
isUploading
}
>
{
isUploading
?
<
LoaderIcon
className=
"w-5 h-5 animate-spin"
/>
:
<
PaperclipIcon
className=
"w-5 h-5"
/>
}
<
input
className=
"absolute inset-0 w-full h-full opacity-0 cursor-pointer"
ref=
{
fileInputRef
}
disabled=
{
isUploading
}
onChange=
{
handleFileInputChange
}
type=
"file"
id=
"files"
multiple=
{
true
}
accept=
"*"
/>
</
Button
>
);
});
export
default
UploadAttachmentButton
;
web/src/components/MemoEditor/ActionButton/UploadResourceButton.tsx
View file @
a4920d46
import
{
Button
}
from
"@usememos/mui"
;
import
{
LoaderIcon
,
PaperclipIcon
}
from
"lucide-react"
;
import
{
observer
}
from
"mobx-react-lite"
;
import
{
useContext
,
useRef
,
useState
}
from
"react"
;
import
toast
from
"react-hot-toast"
;
import
{
attachmentStore
}
from
"@/store/v2"
;
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
MemoEditorContext
}
from
"../types"
;
interface
Props
{
isUploadingResource
?:
boolean
;
}
interface
State
{
uploadingFlag
:
boolean
;
}
const
UploadResourceButton
=
observer
((
props
:
Props
)
=>
{
const
context
=
useContext
(
MemoEditorContext
);
const
[
state
,
setState
]
=
useState
<
State
>
({
uploadingFlag
:
false
,
});
const
fileInputRef
=
useRef
<
HTMLInputElement
>
(
null
);
const
handleFileInputChange
=
async
()
=>
{
if
(
!
fileInputRef
.
current
||
!
fileInputRef
.
current
.
files
||
fileInputRef
.
current
.
files
.
length
===
0
)
{
return
;
}
if
(
state
.
uploadingFlag
)
{
return
;
}
setState
((
state
)
=>
{
return
{
...
state
,
uploadingFlag
:
true
,
};
});
const
createdAttachmentList
:
Attachment
[]
=
[];
try
{
if
(
!
fileInputRef
.
current
||
!
fileInputRef
.
current
.
files
)
{
return
;
}
for
(
const
file
of
fileInputRef
.
current
.
files
)
{
const
{
name
:
filename
,
size
,
type
}
=
file
;
const
buffer
=
new
Uint8Array
(
await
file
.
arrayBuffer
());
const
attachment
=
await
attachmentStore
.
createAttachment
({
attachment
:
Attachment
.
fromPartial
({
filename
,
size
,
type
,
content
:
buffer
,
}),
attachmentId
:
""
,
});
createdAttachmentList
.
push
(
attachment
);
}
}
catch
(
error
:
any
)
{
console
.
error
(
error
);
toast
.
error
(
error
.
details
);
}
context
.
setAttachmentList
([...
context
.
attachmentList
,
...
createdAttachmentList
]);
setState
((
state
)
=>
{
return
{
...
state
,
uploadingFlag
:
false
,
};
});
};
const
isUploading
=
state
.
uploadingFlag
||
props
.
isUploadingResource
;
return
(
<
Button
className=
"relative p-0"
variant=
"plain"
disabled=
{
isUploading
}
>
{
isUploading
?
<
LoaderIcon
className=
"w-5 h-5 animate-spin"
/>
:
<
PaperclipIcon
className=
"w-5 h-5"
/>
}
<
input
className=
"absolute inset-0 w-full h-full opacity-0 cursor-pointer"
ref=
{
fileInputRef
}
disabled=
{
isUploading
}
onChange=
{
handleFileInputChange
}
type=
"file"
id=
"files"
multiple=
{
true
}
accept=
"*"
/>
</
Button
>
);
});
export
default
UploadResourceButton
;
web/src/components/MemoEditor/AttachmentListView.tsx
View file @
a4920d46
...
@@ -2,7 +2,7 @@ import { DndContext, closestCenter, MouseSensor, TouchSensor, useSensor, useSens
...
@@ -2,7 +2,7 @@ import { DndContext, closestCenter, MouseSensor, TouchSensor, useSensor, useSens
import
{
arrayMove
,
SortableContext
,
verticalListSortingStrategy
}
from
"@dnd-kit/sortable"
;
import
{
arrayMove
,
SortableContext
,
verticalListSortingStrategy
}
from
"@dnd-kit/sortable"
;
import
{
XIcon
}
from
"lucide-react"
;
import
{
XIcon
}
from
"lucide-react"
;
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
ResourceIcon
from
"../Resource
Icon"
;
import
AttachmentIcon
from
"../Attachment
Icon"
;
import
SortableItem
from
"./SortableItem"
;
import
SortableItem
from
"./SortableItem"
;
interface
Props
{
interface
Props
{
...
@@ -41,7 +41,7 @@ const AttachmentListView = (props: Props) => {
...
@@ -41,7 +41,7 @@ const AttachmentListView = (props: Props) => {
className=
"max-w-full w-auto flex flex-row justify-start items-center flex-nowrap gap-x-1 bg-zinc-100 dark:bg-zinc-900 px-2 py-1 rounded hover:shadow-sm text-gray-500 dark:text-gray-400"
className=
"max-w-full w-auto flex flex-row justify-start items-center flex-nowrap gap-x-1 bg-zinc-100 dark:bg-zinc-900 px-2 py-1 rounded hover:shadow-sm text-gray-500 dark:text-gray-400"
>
>
<
SortableItem
id=
{
attachment
.
name
}
className=
"flex flex-row justify-start items-center gap-x-1"
>
<
SortableItem
id=
{
attachment
.
name
}
className=
"flex flex-row justify-start items-center gap-x-1"
>
<
ResourceIcon
resource
=
{
attachment
}
className=
"w-4! h-4! opacity-100!"
/>
<
AttachmentIcon
attachment
=
{
attachment
}
className=
"w-4! h-4! opacity-100!"
/>
<
span
className=
"text-sm max-w-32 truncate"
>
{
attachment
.
filename
}
</
span
>
<
span
className=
"text-sm max-w-32 truncate"
>
{
attachment
.
filename
}
</
span
>
</
SortableItem
>
</
SortableItem
>
<
button
className=
"shrink-0"
onClick=
{
()
=>
handleDeleteAttachment
(
attachment
.
name
)
}
>
<
button
className=
"shrink-0"
onClick=
{
()
=>
handleDeleteAttachment
(
attachment
.
name
)
}
>
...
...
web/src/components/MemoEditor/index.tsx
View file @
a4920d46
...
@@ -25,7 +25,7 @@ import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
...
@@ -25,7 +25,7 @@ import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
import
LocationSelector
from
"./ActionButton/LocationSelector"
;
import
LocationSelector
from
"./ActionButton/LocationSelector"
;
import
MarkdownMenu
from
"./ActionButton/MarkdownMenu"
;
import
MarkdownMenu
from
"./ActionButton/MarkdownMenu"
;
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
TagSelector
from
"./ActionButton/TagSelector"
;
import
Upload
ResourceButton
from
"./ActionButton/UploadResource
Button"
;
import
Upload
AttachmentButton
from
"./ActionButton/UploadAttachment
Button"
;
import
VisibilitySelector
from
"./ActionButton/VisibilitySelector"
;
import
VisibilitySelector
from
"./ActionButton/VisibilitySelector"
;
import
AttachmentListView
from
"./AttachmentListView"
;
import
AttachmentListView
from
"./AttachmentListView"
;
import
Editor
,
{
EditorRefActions
}
from
"./Editor"
;
import
Editor
,
{
EditorRefActions
}
from
"./Editor"
;
...
@@ -51,7 +51,7 @@ interface State {
...
@@ -51,7 +51,7 @@ interface State {
attachmentList
:
Attachment
[];
attachmentList
:
Attachment
[];
relationList
:
MemoRelation
[];
relationList
:
MemoRelation
[];
location
:
Location
|
undefined
;
location
:
Location
|
undefined
;
isUploading
Resource
:
boolean
;
isUploading
Attachment
:
boolean
;
isRequesting
:
boolean
;
isRequesting
:
boolean
;
isComposing
:
boolean
;
isComposing
:
boolean
;
isDraggingFile
:
boolean
;
isDraggingFile
:
boolean
;
...
@@ -67,7 +67,7 @@ const MemoEditor = observer((props: Props) => {
...
@@ -67,7 +67,7 @@ const MemoEditor = observer((props: Props) => {
attachmentList
:
[],
attachmentList
:
[],
relationList
:
[],
relationList
:
[],
location
:
undefined
,
location
:
undefined
,
isUploading
Resource
:
false
,
isUploading
Attachment
:
false
,
isRequesting
:
false
,
isRequesting
:
false
,
isComposing
:
false
,
isComposing
:
false
,
isDraggingFile
:
false
,
isDraggingFile
:
false
,
...
@@ -203,7 +203,7 @@ const MemoEditor = observer((props: Props) => {
...
@@ -203,7 +203,7 @@ const MemoEditor = observer((props: Props) => {
setState
((
state
)
=>
{
setState
((
state
)
=>
{
return
{
return
{
...
state
,
...
state
,
isUploading
Resource
:
true
,
isUploading
Attachment
:
true
,
};
};
});
});
...
@@ -223,7 +223,7 @@ const MemoEditor = observer((props: Props) => {
...
@@ -223,7 +223,7 @@ const MemoEditor = observer((props: Props) => {
setState
((
state
)
=>
{
setState
((
state
)
=>
{
return
{
return
{
...
state
,
...
state
,
isUploading
Resource
:
false
,
isUploading
Attachment
:
false
,
};
};
});
});
return
attachment
;
return
attachment
;
...
@@ -233,7 +233,7 @@ const MemoEditor = observer((props: Props) => {
...
@@ -233,7 +233,7 @@ const MemoEditor = observer((props: Props) => {
setState
((
state
)
=>
{
setState
((
state
)
=>
{
return
{
return
{
...
state
,
...
state
,
isUploading
Resource
:
false
,
isUploading
Attachment
:
false
,
};
};
});
});
}
}
...
@@ -456,7 +456,7 @@ const MemoEditor = observer((props: Props) => {
...
@@ -456,7 +456,7 @@ const MemoEditor = observer((props: Props) => {
[
i18n
.
language
],
[
i18n
.
language
],
);
);
const
allowSave
=
(
hasContent
||
state
.
attachmentList
.
length
>
0
)
&&
!
state
.
isUploading
Resource
&&
!
state
.
isRequesting
;
const
allowSave
=
(
hasContent
||
state
.
attachmentList
.
length
>
0
)
&&
!
state
.
isUploading
Attachment
&&
!
state
.
isRequesting
;
return
(
return
(
<
MemoEditorContext
.
Provider
<
MemoEditorContext
.
Provider
...
@@ -502,7 +502,7 @@ const MemoEditor = observer((props: Props) => {
...
@@ -502,7 +502,7 @@ const MemoEditor = observer((props: Props) => {
<
div
className=
"flex flex-row justify-start items-center opacity-80 dark:opacity-60 space-x-2"
>
<
div
className=
"flex flex-row justify-start items-center opacity-80 dark:opacity-60 space-x-2"
>
<
TagSelector
editorRef=
{
editorRef
}
/>
<
TagSelector
editorRef=
{
editorRef
}
/>
<
MarkdownMenu
editorRef=
{
editorRef
}
/>
<
MarkdownMenu
editorRef=
{
editorRef
}
/>
<
Upload
ResourceButton
isUploadingResource=
{
state
.
isUploadingResource
}
/>
<
Upload
AttachmentButton
isUploading=
{
state
.
isUploadingAttachment
}
/>
<
AddMemoRelationPopover
editorRef=
{
editorRef
}
/>
<
AddMemoRelationPopover
editorRef=
{
editorRef
}
/>
<
LocationSelector
<
LocationSelector
location=
{
state
.
location
}
location=
{
state
.
location
}
...
...
web/src/components/MemoResource.tsx
View file @
a4920d46
import
{
Attachment
}
from
"@/types/proto/api/v1/attachment_service"
;
import
{
getAttachmentUrl
}
from
"@/utils/attachment"
;
import
ResourceIcon
from
"./ResourceIcon"
;
interface
Props
{
resource
:
Attachment
;
className
?:
string
;
}
const
MemoResource
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
className
,
resource
}
=
props
;
const
resourceUrl
=
getAttachmentUrl
(
resource
);
const
handlePreviewBtnClick
=
()
=>
{
window
.
open
(
resourceUrl
);
};
return
(
<
div
className=
{
`w-auto flex flex-row justify-start items-center text-gray-500 dark:text-gray-400 hover:opacity-80 ${className}`
}
>
{
resource
.
type
.
startsWith
(
"audio"
)
?
(
<
audio
src=
{
resourceUrl
}
controls
></
audio
>
)
:
(
<>
<
ResourceIcon
className=
"w-4! h-4! mr-1"
resource=
{
resource
}
/>
<
span
className=
"text-sm max-w-[256px] truncate cursor-pointer"
onClick=
{
handlePreviewBtnClick
}
>
{
resource
.
filename
}
</
span
>
</>
)
}
</
div
>
);
};
export
default
MemoResource
;
web/src/pages/Attachments.tsx
View file @
a4920d46
...
@@ -5,9 +5,9 @@ import { includes } from "lodash-es";
...
@@ -5,9 +5,9 @@ import { includes } from "lodash-es";
import
{
PaperclipIcon
,
SearchIcon
,
TrashIcon
}
from
"lucide-react"
;
import
{
PaperclipIcon
,
SearchIcon
,
TrashIcon
}
from
"lucide-react"
;
import
{
observer
}
from
"mobx-react-lite"
;
import
{
observer
}
from
"mobx-react-lite"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
AttachmentIcon
from
"@/components/AttachmentIcon"
;
import
Empty
from
"@/components/Empty"
;
import
Empty
from
"@/components/Empty"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
MobileHeader
from
"@/components/MobileHeader"
;
import
ResourceIcon
from
"@/components/ResourceIcon"
;
import
{
attachmentServiceClient
}
from
"@/grpcweb"
;
import
{
attachmentServiceClient
}
from
"@/grpcweb"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
useLoading
from
"@/hooks/useLoading"
;
import
useResponsiveWidth
from
"@/hooks/useResponsiveWidth"
;
import
useResponsiveWidth
from
"@/hooks/useResponsiveWidth"
;
...
@@ -112,7 +112,7 @@ const Attachments = observer(() => {
...
@@ -112,7 +112,7 @@ const Attachments = observer(() => {
return
(
return
(
<
div
key=
{
attachment
.
name
}
className=
"w-24 sm:w-32 h-auto flex flex-col justify-start items-start"
>
<
div
key=
{
attachment
.
name
}
className=
"w-24 sm:w-32 h-auto flex flex-col justify-start items-start"
>
<
div
className=
"w-24 h-24 flex justify-center items-center sm:w-32 sm:h-32 border border-zinc-200 dark:border-zinc-900 overflow-clip rounded-xl cursor-pointer hover:shadow hover:opacity-80"
>
<
div
className=
"w-24 h-24 flex justify-center items-center sm:w-32 sm:h-32 border border-zinc-200 dark:border-zinc-900 overflow-clip rounded-xl cursor-pointer hover:shadow hover:opacity-80"
>
<
ResourceIcon
resource
=
{
attachment
}
strokeWidth=
{
0.5
}
/>
<
AttachmentIcon
attachment
=
{
attachment
}
strokeWidth=
{
0.5
}
/>
</
div
>
</
div
>
<
div
className=
"w-full max-w-full flex flex-row justify-between items-center mt-1 px-1"
>
<
div
className=
"w-full max-w-full flex flex-row justify-between items-center mt-1 px-1"
>
<
p
className=
"text-xs shrink text-gray-400 truncate"
>
{
attachment
.
filename
}
</
p
>
<
p
className=
"text-xs shrink text-gray-400 truncate"
>
{
attachment
.
filename
}
</
p
>
...
@@ -144,7 +144,7 @@ const Attachments = observer(() => {
...
@@ -144,7 +144,7 @@ const Attachments = observer(() => {
return
(
return
(
<
div
key=
{
attachment
.
name
}
className=
"w-24 sm:w-32 h-auto flex flex-col justify-start items-start"
>
<
div
key=
{
attachment
.
name
}
className=
"w-24 sm:w-32 h-auto flex flex-col justify-start items-start"
>
<
div
className=
"w-24 h-24 flex justify-center items-center sm:w-32 sm:h-32 border border-zinc-200 dark:border-zinc-900 overflow-clip rounded-xl cursor-pointer hover:shadow hover:opacity-80"
>
<
div
className=
"w-24 h-24 flex justify-center items-center sm:w-32 sm:h-32 border border-zinc-200 dark:border-zinc-900 overflow-clip rounded-xl cursor-pointer hover:shadow hover:opacity-80"
>
<
ResourceIcon
resource
=
{
attachment
}
strokeWidth=
{
0.5
}
/>
<
AttachmentIcon
attachment
=
{
attachment
}
strokeWidth=
{
0.5
}
/>
</
div
>
</
div
>
<
div
className=
"w-full max-w-full flex flex-row justify-between items-center mt-1 px-1"
>
<
div
className=
"w-full max-w-full flex flex-row justify-between items-center mt-1 px-1"
>
<
p
className=
"text-xs shrink text-gray-400 truncate"
>
{
attachment
.
filename
}
</
p
>
<
p
className=
"text-xs shrink text-gray-400 truncate"
>
{
attachment
.
filename
}
</
p
>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment