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
91c2a4ce
Commit
91c2a4ce
authored
Jun 18, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: inbox service
parent
a4920d46
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1175 additions
and
151 deletions
+1175
-151
inbox_service.proto
proto/api/v1/inbox_service.proto
+82
-22
inbox_service.pb.go
proto/gen/api/v1/inbox_service.pb.go
+119
-48
inbox_service.pb.gw.go
proto/gen/api/v1/inbox_service.pb.gw.go
+22
-4
apidocs.swagger.yaml
proto/gen/apidocs.swagger.yaml
+120
-43
common.go
server/router/api/v1/common.go
+2
-0
inbox_service.go
server/router/api/v1/inbox_service.go
+90
-11
inbox_service_test.go
server/router/api/v1/test/inbox_service_test.go
+559
-0
Inboxes.tsx
web/src/pages/Inboxes.tsx
+10
-1
user.ts
web/src/store/v2/user.ts
+8
-1
inbox_service.ts
web/src/types/proto/api/v1/inbox_service.ts
+163
-21
No files found.
proto/api/v1/inbox_service.proto
View file @
91c2a4ce
...
...
@@ -4,6 +4,8 @@ package memos.api.v1;
import
"google/api/annotations.proto"
;
import
"google/api/client.proto"
;
import
"google/api/field_behavior.proto"
;
import
"google/api/resource.proto"
;
import
"google/protobuf/empty.proto"
;
import
"google/protobuf/field_mask.proto"
;
import
"google/protobuf/timestamp.proto"
;
...
...
@@ -13,7 +15,8 @@ option go_package = "gen/api/v1";
service
InboxService
{
// ListInboxes lists inboxes for a user.
rpc
ListInboxes
(
ListInboxesRequest
)
returns
(
ListInboxesResponse
)
{
option
(
google.api.http
)
=
{
get
:
"/api/v1/inboxes"
};
option
(
google.api.http
)
=
{
get
:
"/api/v1/{parent=users/*}/inboxes"
};
option
(
google.api.method_signature
)
=
"parent"
;
}
// UpdateInbox updates an inbox.
rpc
UpdateInbox
(
UpdateInboxRequest
)
returns
(
Inbox
)
{
...
...
@@ -31,59 +34,116 @@ service InboxService {
}
message
Inbox
{
// The name of the inbox.
// Format: inboxes/{id}, id is the system generated auto-incremented id.
string
name
=
1
;
option
(
google.api.resource
)
=
{
type
:
"memos.api.v1/Inbox"
pattern
:
"inboxes/{inbox}"
name_field
:
"name"
singular
:
"inbox"
plural
:
"inboxes"
};
// The resource name of the inbox.
// Format: inboxes/{inbox}
string
name
=
1
[(
google.api.field_behavior
)
=
IDENTIFIER
];
// The sender of the inbox notification.
// Format: users/{user}
string
sender
=
2
;
string
sender
=
2
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The receiver of the inbox notification.
// Format: users/{user}
string
receiver
=
3
;
string
receiver
=
3
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The status of the inbox notification.
Status
status
=
4
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Output only. The creation timestamp.
google.protobuf.Timestamp
create_time
=
5
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The type of the inbox notification.
Type
type
=
6
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// Optional. The activity ID associated with this inbox notification.
optional
int32
activity_id
=
7
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Status enumeration for inbox notifications.
enum
Status
{
// Unspecified status.
STATUS_UNSPECIFIED
=
0
;
// The notification is unread.
UNREAD
=
1
;
// The notification is archived.
ARCHIVED
=
2
;
}
Status
status
=
4
;
google.protobuf.Timestamp
create_time
=
5
;
// Type enumeration for inbox notifications.
enum
Type
{
// Unspecified type.
TYPE_UNSPECIFIED
=
0
;
// Memo comment notification.
MEMO_COMMENT
=
1
;
// Version update notification.
VERSION_UPDATE
=
2
;
}
Type
type
=
6
;
optional
int32
activity_id
=
7
;
}
message
ListInboxesRequest
{
// Required. The parent resource whose inboxes will be listed.
// Format: users/{user}
string
user
=
1
;
string
parent
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/User"
}
];
// Optional. The maximum number of inboxes to return.
// The service may return fewer than this value.
// If unspecified, at most 50 inboxes will be returned.
// The maximum value is 1000; values above 1000 will be coerced to 1000.
int32
page_size
=
2
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. A page token, received from a previous `ListInboxes` call.
// Provide this to retrieve the subsequent page.
string
page_token
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
// The maximum number of inbox to return.
int32
page_size
=
2
;
// Optional. Filter to apply to the list results.
// Example: "status=UNREAD" or "type=MEMO_COMMENT"
// Supported operators: =, !=
// Supported fields: status, type, sender, create_time
string
filter
=
4
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Provide this to retrieve the subsequent page.
string
page_token
=
3
;
// Optional. The order to sort results by.
// Example: "create_time desc" or "status asc"
string
order_by
=
5
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
ListInboxesResponse
{
// The list of inboxes.
repeated
Inbox
inboxes
=
1
;
// A token
, which
can be sent as `page_token` to retrieve the next page.
// A token
that
can be sent as `page_token` to retrieve the next page.
// If this field is omitted, there are no subsequent pages.
string
next_page_token
=
2
;
// The total count of inboxes (may be approximate).
int32
total_size
=
3
;
}
message
UpdateInboxRequest
{
Inbox
inbox
=
1
;
// Required. The inbox to update.
Inbox
inbox
=
1
[(
google.api.field_behavior
)
=
REQUIRED
];
// Required. The list of fields to update.
google.protobuf.FieldMask
update_mask
=
2
[(
google.api.field_behavior
)
=
REQUIRED
];
google.protobuf.FieldMask
update_mask
=
2
;
// Optional. If set to true, allows updating missing fields.
bool
allow_missing
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
DeleteInboxRequest
{
// The name of the inbox to delete.
string
name
=
1
;
// Required. The resource name of the inbox to delete.
// Format: inboxes/{inbox}
string
name
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/Inbox"
}
];
}
proto/gen/api/v1/inbox_service.pb.go
View file @
91c2a4ce
...
...
@@ -25,12 +25,16 @@ const (
_
=
protoimpl
.
EnforceVersion
(
protoimpl
.
MaxVersion
-
20
)
)
// Status enumeration for inbox notifications.
type
Inbox_Status
int32
const
(
// Unspecified status.
Inbox_STATUS_UNSPECIFIED
Inbox_Status
=
0
Inbox_UNREAD
Inbox_Status
=
1
Inbox_ARCHIVED
Inbox_Status
=
2
// The notification is unread.
Inbox_UNREAD
Inbox_Status
=
1
// The notification is archived.
Inbox_ARCHIVED
Inbox_Status
=
2
)
// Enum value maps for Inbox_Status.
...
...
@@ -74,12 +78,16 @@ func (Inbox_Status) EnumDescriptor() ([]byte, []int) {
return
file_api_v1_inbox_service_proto_rawDescGZIP
(),
[]
int
{
0
,
0
}
}
// Type enumeration for inbox notifications.
type
Inbox_Type
int32
const
(
// Unspecified type.
Inbox_TYPE_UNSPECIFIED
Inbox_Type
=
0
Inbox_MEMO_COMMENT
Inbox_Type
=
1
Inbox_VERSION_UPDATE
Inbox_Type
=
2
// Memo comment notification.
Inbox_MEMO_COMMENT
Inbox_Type
=
1
// Version update notification.
Inbox_VERSION_UPDATE
Inbox_Type
=
2
)
// Enum value maps for Inbox_Type.
...
...
@@ -125,17 +133,23 @@ func (Inbox_Type) EnumDescriptor() ([]byte, []int) {
type
Inbox
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
// The name of the inbox.
// Format: inboxes/{i
d}, id is the system generated auto-incremented id.
// The
resource
name of the inbox.
// Format: inboxes/{i
nbox}
Name
string
`protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// The sender of the inbox notification.
// Format: users/{user}
Sender
string
`protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
// The receiver of the inbox notification.
// Format: users/{user}
Receiver
string
`protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"`
Status
Inbox_Status
`protobuf:"varint,4,opt,name=status,proto3,enum=memos.api.v1.Inbox_Status" json:"status,omitempty"`
CreateTime
*
timestamppb
.
Timestamp
`protobuf:"bytes,5,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"`
Type
Inbox_Type
`protobuf:"varint,6,opt,name=type,proto3,enum=memos.api.v1.Inbox_Type" json:"type,omitempty"`
ActivityId
*
int32
`protobuf:"varint,7,opt,name=activity_id,json=activityId,proto3,oneof" json:"activity_id,omitempty"`
Receiver
string
`protobuf:"bytes,3,opt,name=receiver,proto3" json:"receiver,omitempty"`
// The status of the inbox notification.
Status
Inbox_Status
`protobuf:"varint,4,opt,name=status,proto3,enum=memos.api.v1.Inbox_Status" json:"status,omitempty"`
// Output only. The creation timestamp.
CreateTime
*
timestamppb
.
Timestamp
`protobuf:"bytes,5,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"`
// The type of the inbox notification.
Type
Inbox_Type
`protobuf:"varint,6,opt,name=type,proto3,enum=memos.api.v1.Inbox_Type" json:"type,omitempty"`
// Optional. The activity ID associated with this inbox notification.
ActivityId
*
int32
`protobuf:"varint,7,opt,name=activity_id,json=activityId,proto3,oneof" json:"activity_id,omitempty"`
unknownFields
protoimpl
.
UnknownFields
sizeCache
protoimpl
.
SizeCache
}
...
...
@@ -221,12 +235,25 @@ func (x *Inbox) GetActivityId() int32 {
type
ListInboxesRequest
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
// Required. The parent resource whose inboxes will be listed.
// Format: users/{user}
User
string
`protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
// The maximum number of inbox to return.
Parent
string
`protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"`
// Optional. The maximum number of inboxes to return.
// The service may return fewer than this value.
// If unspecified, at most 50 inboxes will be returned.
// The maximum value is 1000; values above 1000 will be coerced to 1000.
PageSize
int32
`protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
// Optional. A page token, received from a previous `ListInboxes` call.
// Provide this to retrieve the subsequent page.
PageToken
string
`protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
PageToken
string
`protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
// Optional. Filter to apply to the list results.
// Example: "status=UNREAD" or "type=MEMO_COMMENT"
// Supported operators: =, !=
// Supported fields: status, type, sender, create_time
Filter
string
`protobuf:"bytes,4,opt,name=filter,proto3" json:"filter,omitempty"`
// Optional. The order to sort results by.
// Example: "create_time desc" or "status asc"
OrderBy
string
`protobuf:"bytes,5,opt,name=order_by,json=orderBy,proto3" json:"order_by,omitempty"`
unknownFields
protoimpl
.
UnknownFields
sizeCache
protoimpl
.
SizeCache
}
...
...
@@ -261,9 +288,9 @@ func (*ListInboxesRequest) Descriptor() ([]byte, []int) {
return
file_api_v1_inbox_service_proto_rawDescGZIP
(),
[]
int
{
1
}
}
func
(
x
*
ListInboxesRequest
)
Get
User
()
string
{
func
(
x
*
ListInboxesRequest
)
Get
Parent
()
string
{
if
x
!=
nil
{
return
x
.
User
return
x
.
Parent
}
return
""
}
...
...
@@ -282,12 +309,29 @@ func (x *ListInboxesRequest) GetPageToken() string {
return
""
}
func
(
x
*
ListInboxesRequest
)
GetFilter
()
string
{
if
x
!=
nil
{
return
x
.
Filter
}
return
""
}
func
(
x
*
ListInboxesRequest
)
GetOrderBy
()
string
{
if
x
!=
nil
{
return
x
.
OrderBy
}
return
""
}
type
ListInboxesResponse
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
Inboxes
[]
*
Inbox
`protobuf:"bytes,1,rep,name=inboxes,proto3" json:"inboxes,omitempty"`
// A token, which can be sent as `page_token` to retrieve the next page.
state
protoimpl
.
MessageState
`protogen:"open.v1"`
// The list of inboxes.
Inboxes
[]
*
Inbox
`protobuf:"bytes,1,rep,name=inboxes,proto3" json:"inboxes,omitempty"`
// A token that can be sent as `page_token` to retrieve the next page.
// If this field is omitted, there are no subsequent pages.
NextPageToken
string
`protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
// The total count of inboxes (may be approximate).
TotalSize
int32
`protobuf:"varint,3,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"`
unknownFields
protoimpl
.
UnknownFields
sizeCache
protoimpl
.
SizeCache
}
...
...
@@ -336,10 +380,21 @@ func (x *ListInboxesResponse) GetNextPageToken() string {
return
""
}
func
(
x
*
ListInboxesResponse
)
GetTotalSize
()
int32
{
if
x
!=
nil
{
return
x
.
TotalSize
}
return
0
}
type
UpdateInboxRequest
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
Inbox
*
Inbox
`protobuf:"bytes,1,opt,name=inbox,proto3" json:"inbox,omitempty"`
UpdateMask
*
fieldmaskpb
.
FieldMask
`protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"`
state
protoimpl
.
MessageState
`protogen:"open.v1"`
// Required. The inbox to update.
Inbox
*
Inbox
`protobuf:"bytes,1,opt,name=inbox,proto3" json:"inbox,omitempty"`
// Required. The list of fields to update.
UpdateMask
*
fieldmaskpb
.
FieldMask
`protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"`
// Optional. If set to true, allows updating missing fields.
AllowMissing
bool
`protobuf:"varint,3,opt,name=allow_missing,json=allowMissing,proto3" json:"allow_missing,omitempty"`
unknownFields
protoimpl
.
UnknownFields
sizeCache
protoimpl
.
SizeCache
}
...
...
@@ -388,9 +443,17 @@ func (x *UpdateInboxRequest) GetUpdateMask() *fieldmaskpb.FieldMask {
return
nil
}
func
(
x
*
UpdateInboxRequest
)
GetAllowMissing
()
bool
{
if
x
!=
nil
{
return
x
.
AllowMissing
}
return
false
}
type
DeleteInboxRequest
struct
{
state
protoimpl
.
MessageState
`protogen:"open.v1"`
// The name of the inbox to delete.
// Required. The resource name of the inbox to delete.
// Format: inboxes/{inbox}
Name
string
`protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields
protoimpl
.
UnknownFields
sizeCache
protoimpl
.
SizeCache
...
...
@@ -437,16 +500,16 @@ var File_api_v1_inbox_service_proto protoreflect.FileDescriptor
const
file_api_v1_inbox_service_proto_rawDesc
=
""
+
"
\n
"
+
"
\x1a
api/v1/inbox_service.proto
\x12\f
memos.api.v1
\x1a\x1c
google/api/annotations.proto
\x1a\x17
google/api/client.proto
\x1a\x1
b
google/protobuf/empty.proto
\x1a
google/protobuf/field_mask.proto
\x1a\x1f
google/protobuf/timestamp.proto
\"\xa4\x03
\n
"
+
"
\x05
Inbox
\x12\x1
2
\n
"
+
"
\x04
name
\x18\x01
\x01
(
\t
R
\x04
name
\x12\x16
\n
"
+
"
\x06
sender
\x18\x02
\x01
(
\t
R
\x06
sender
\x12\x1a
\n
"
+
"
\b
receiver
\x18\x03
\x01
(
\t
R
\b
receiver
\x12
2
\n
"
+
"
\x06
status
\x18\x04
\x01
(
\x0e
2
\x1a
.memos.api.v1.Inbox.Status
R
\x06
status
\x12
;
\n
"
+
"
\v
create_time
\x18\x05
\x01
(
\v
2
\x1a
.google.protobuf.TimestampR
\n
"
+
"createTime
\x12
,
\n
"
+
"
\x04
type
\x18\x06
\x01
(
\x0e
2
\x18
.memos.api.v1.Inbox.Type
R
\x04
type
\x12
$
\n
"
+
"
\v
activity_id
\x18\a
\x01
(
\x05
H
\x00
R
\n
"
+
"
\x1a
api/v1/inbox_service.proto
\x12\f
memos.api.v1
\x1a\x1c
google/api/annotations.proto
\x1a\x17
google/api/client.proto
\x1a\x1
f
google/api/field_behavior.proto
\x1a\x19
google/api/resource.proto
\x1a\x1b
google/protobuf/empty.proto
\x1a
google/protobuf/field_mask.proto
\x1a\x1f
google/protobuf/timestamp.proto
\"\x87\x04
\n
"
+
"
\x05
Inbox
\x12\x1
7
\n
"
+
"
\x04
name
\x18\x01
\x01
(
\t
B
\x03\xe0
A
\b
R
\x04
name
\x12\x1b
\n
"
+
"
\x06
sender
\x18\x02
\x01
(
\t
B
\x03\xe0
A
\x03
R
\x06
sender
\x12\x1f
\n
"
+
"
\b
receiver
\x18\x03
\x01
(
\t
B
\x03\xe0
A
\x03
R
\b
receiver
\x12
7
\n
"
+
"
\x06
status
\x18\x04
\x01
(
\x0e
2
\x1a
.memos.api.v1.Inbox.Status
B
\x03\xe0
A
\x01
R
\x06
status
\x12
@
\n
"
+
"
\v
create_time
\x18\x05
\x01
(
\v
2
\x1a
.google.protobuf.Timestamp
B
\x03\xe0
A
\x03
R
\n
"
+
"createTime
\x12
1
\n
"
+
"
\x04
type
\x18\x06
\x01
(
\x0e
2
\x18
.memos.api.v1.Inbox.Type
B
\x03\xe0
A
\x03
R
\x04
type
\x12
)
\n
"
+
"
\v
activity_id
\x18\a
\x01
(
\x05
B
\x03\xe0
A
\x01
H
\x00
R
\n
"
+
"activityId
\x88\x01\x01\"
:
\n
"
+
"
\x06
Status
\x12\x16\n
"
+
"
\x12
STATUS_UNSPECIFIED
\x10\x00\x12\n
"
+
...
...
@@ -456,24 +519,32 @@ const file_api_v1_inbox_service_proto_rawDesc = "" +
"
\x04
Type
\x12\x14\n
"
+
"
\x10
TYPE_UNSPECIFIED
\x10\x00\x12\x10\n
"
+
"
\f
MEMO_COMMENT
\x10\x01\x12\x12\n
"
+
"
\x0e
VERSION_UPDATE
\x10\x02
B
\x0e\n
"
+
"
\f
_activity_id
\"
d
\n
"
+
"
\x12
ListInboxesRequest
\x12\x12\n
"
+
"
\x04
user
\x18\x01
\x01
(
\t
R
\x04
user
\x12\x1b\n
"
+
"
\t
page_size
\x18\x02
\x01
(
\x05
R
\b
pageSize
\x12\x1d\n
"
+
"
\x0e
VERSION_UPDATE
\x10\x02
:>
\xea
A;
\n
"
+
"
\x12
memos.api.v1/Inbox
\x12\x0f
inboxes/{inbox}
\x1a\x04
name*
\a
inboxes2
\x05
inboxB
\x0e\n
"
+
"
\f
_activity_id
\"\xca\x01\n
"
+
"
\x12
ListInboxesRequest
\x12
1
\n
"
+
"
\x06
parent
\x18\x01
\x01
(
\t
B
\x19\xe0
A
\x02\xfa
A
\x13\n
"
+
"
\x11
memos.api.v1/UserR
\x06
parent
\x12
\n
"
+
"
\t
page_size
\x18\x02
\x01
(
\x05
B
\x03\xe0
A
\x01
R
\b
pageSize
\x12\"\n
"
+
"
\n
"
+
"page_token
\x18\x03
\x01
(
\t
R
\t
pageToken
\"
l
\n
"
+
"page_token
\x18\x03
\x01
(
\t
B
\x03\xe0
A
\x01
R
\t
pageToken
\x12\x1b\n
"
+
"
\x06
filter
\x18\x04
\x01
(
\t
B
\x03\xe0
A
\x01
R
\x06
filter
\x12\x1e\n
"
+
"
\b
order_by
\x18\x05
\x01
(
\t
B
\x03\xe0
A
\x01
R
\a
orderBy
\"\x8b\x01\n
"
+
"
\x13
ListInboxesResponse
\x12
-
\n
"
+
"
\a
inboxes
\x18\x01
\x03
(
\v
2
\x13
.memos.api.v1.InboxR
\a
inboxes
\x12
&
\n
"
+
"
\x0f
next_page_token
\x18\x02
\x01
(
\t
R
\r
nextPageToken
\"
|
\n
"
+
"
\x12
UpdateInboxRequest
\x12
)
\n
"
+
"
\x05
inbox
\x18\x01
\x01
(
\v
2
\x13
.memos.api.v1.InboxR
\x05
inbox
\x12
;
\n
"
+
"
\v
update_mask
\x18\x02
\x01
(
\v
2
\x1a
.google.protobuf.FieldMaskR
\n
"
+
"updateMask
\"
(
\n
"
+
"
\x12
DeleteInboxRequest
\x12\x12\n
"
+
"
\x04
name
\x18\x01
\x01
(
\t
R
\x04
name2
\xf7\x02\n
"
+
"
\f
InboxService
\x12
k
\n
"
+
"
\v
ListInboxes
\x12
.memos.api.v1.ListInboxesRequest
\x1a
!.memos.api.v1.ListInboxesResponse
\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f
/api/v1/inboxes
\x12\x87\x01\n
"
+
"
\x0f
next_page_token
\x18\x02
\x01
(
\t
R
\r
nextPageToken
\x12\x1d\n
"
+
"
\n
"
+
"total_size
\x18\x03
\x01
(
\x05
R
\t
totalSize
\"\xb0\x01\n
"
+
"
\x12
UpdateInboxRequest
\x12
.
\n
"
+
"
\x05
inbox
\x18\x01
\x01
(
\v
2
\x13
.memos.api.v1.InboxB
\x03\xe0
A
\x02
R
\x05
inbox
\x12
@
\n
"
+
"
\v
update_mask
\x18\x02
\x01
(
\v
2
\x1a
.google.protobuf.FieldMaskB
\x03\xe0
A
\x02
R
\n
"
+
"updateMask
\x12
(
\n
"
+
"
\r
allow_missing
\x18\x03
\x01
(
\b
B
\x03\xe0
A
\x01
R
\f
allowMissing
\"
D
\n
"
+
"
\x12
DeleteInboxRequest
\x12
.
\n
"
+
"
\x04
name
\x18\x01
\x01
(
\t
B
\x1a\xe0
A
\x02\xfa
A
\x14\n
"
+
"
\x12
memos.api.v1/InboxR
\x04
name2
\x92\x03\n
"
+
"
\f
InboxService
\x12\x85\x01\n
"
+
"
\v
ListInboxes
\x12
.memos.api.v1.ListInboxesRequest
\x1a
!.memos.api.v1.ListInboxesResponse
\"
1
\xda
A
\x06
parent
\x82\xd3\xe4\x93\x02\"\x12
/api/v1/{parent=users/*}/inboxes
\x12\x87\x01\n
"
+
"
\v
UpdateInbox
\x12
.memos.api.v1.UpdateInboxRequest
\x1a\x13
.memos.api.v1.Inbox
\"
A
\xda
A
\x11
inbox,update_mask
\x82\xd3\xe4\x93\x02
':
\x05
inbox2
\x1e
/api/v1/{inbox.name=inboxes/*}
\x12
p
\n
"
+
"
\v
DeleteInbox
\x12
.memos.api.v1.DeleteInboxRequest
\x1a\x16
.google.protobuf.Empty
\"
'
\xda
A
\x04
name
\x82\xd3\xe4\x93\x02\x1a
*
\x18
/api/v1/{name=inboxes/*}B
\xa9\x01\n
"
+
"
\x10
com.memos.api.v1B
\x11
InboxServiceProtoP
\x01
Z0github.com/usememos/memos/proto/gen/api/v1;apiv1
\xa2\x02\x03
MAX
\xaa\x02\f
Memos.Api.V1
\xca\x02\f
Memos
\\
Api
\\
V1
\xe2\x02\x18
Memos
\\
Api
\\
V1
\\
GPBMetadata
\xea\x02\x0e
Memos::Api::V1b
\x06
proto3"
...
...
proto/gen/api/v1/inbox_service.pb.gw.go
View file @
91c2a4ce
...
...
@@ -35,14 +35,23 @@ var (
_
=
metadata
.
Join
)
var
filter_InboxService_ListInboxes_0
=
&
utilities
.
DoubleArray
{
Encoding
:
map
[
string
]
int
{
},
Base
:
[]
int
(
nil
),
Check
:
[]
int
(
nil
)
}
var
filter_InboxService_ListInboxes_0
=
&
utilities
.
DoubleArray
{
Encoding
:
map
[
string
]
int
{
"parent"
:
0
},
Base
:
[]
int
{
1
,
1
,
0
},
Check
:
[]
int
{
0
,
1
,
2
}
}
func
request_InboxService_ListInboxes_0
(
ctx
context
.
Context
,
marshaler
runtime
.
Marshaler
,
client
InboxServiceClient
,
req
*
http
.
Request
,
pathParams
map
[
string
]
string
)
(
proto
.
Message
,
runtime
.
ServerMetadata
,
error
)
{
var
(
protoReq
ListInboxesRequest
metadata
runtime
.
ServerMetadata
err
error
)
io
.
Copy
(
io
.
Discard
,
req
.
Body
)
val
,
ok
:=
pathParams
[
"parent"
]
if
!
ok
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"missing parameter %s"
,
"parent"
)
}
protoReq
.
Parent
,
err
=
runtime
.
String
(
val
)
if
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"type mismatch, parameter: %s, error: %v"
,
"parent"
,
err
)
}
if
err
:=
req
.
ParseForm
();
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"%v"
,
err
)
}
...
...
@@ -57,7 +66,16 @@ func local_request_InboxService_ListInboxes_0(ctx context.Context, marshaler run
var
(
protoReq
ListInboxesRequest
metadata
runtime
.
ServerMetadata
err
error
)
val
,
ok
:=
pathParams
[
"parent"
]
if
!
ok
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"missing parameter %s"
,
"parent"
)
}
protoReq
.
Parent
,
err
=
runtime
.
String
(
val
)
if
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"type mismatch, parameter: %s, error: %v"
,
"parent"
,
err
)
}
if
err
:=
req
.
ParseForm
();
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"%v"
,
err
)
}
...
...
@@ -195,7 +213,7 @@ func RegisterInboxServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
var
stream
runtime
.
ServerTransportStream
ctx
=
grpc
.
NewContextWithServerTransportStream
(
ctx
,
&
stream
)
inboundMarshaler
,
outboundMarshaler
:=
runtime
.
MarshalerForRequest
(
mux
,
req
)
annotatedContext
,
err
:=
runtime
.
AnnotateIncomingContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/inboxes"
))
annotatedContext
,
err
:=
runtime
.
AnnotateIncomingContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/
{parent=users/*}/
inboxes"
))
if
err
!=
nil
{
runtime
.
HTTPError
(
ctx
,
mux
,
outboundMarshaler
,
w
,
req
,
err
)
return
...
...
@@ -293,7 +311,7 @@ func RegisterInboxServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
ctx
,
cancel
:=
context
.
WithCancel
(
req
.
Context
())
defer
cancel
()
inboundMarshaler
,
outboundMarshaler
:=
runtime
.
MarshalerForRequest
(
mux
,
req
)
annotatedContext
,
err
:=
runtime
.
AnnotateContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/inboxes"
))
annotatedContext
,
err
:=
runtime
.
AnnotateContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/
{parent=users/*}/
inboxes"
))
if
err
!=
nil
{
runtime
.
HTTPError
(
ctx
,
mux
,
outboundMarshaler
,
w
,
req
,
err
)
return
...
...
@@ -344,7 +362,7 @@ func RegisterInboxServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
}
var
(
pattern_InboxService_ListInboxes_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
},
[]
string
{
"api"
,
"v1
"
,
"inboxes"
},
""
))
pattern_InboxService_ListInboxes_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
,
1
,
0
,
4
,
2
,
5
,
3
,
2
,
4
},
[]
string
{
"api"
,
"v1"
,
"users"
,
"parent
"
,
"inboxes"
},
""
))
pattern_InboxService_UpdateInbox_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
,
1
,
0
,
4
,
2
,
5
,
3
},
[]
string
{
"api"
,
"v1"
,
"inboxes"
,
"inbox.name"
},
""
))
pattern_InboxService_DeleteInbox_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
,
1
,
0
,
4
,
2
,
5
,
3
},
[]
string
{
"api"
,
"v1"
,
"inboxes"
,
"name"
},
""
))
)
...
...
proto/gen/apidocs.swagger.yaml
View file @
91c2a4ce
...
...
@@ -292,38 +292,6 @@ paths:
type: string
tags
:
-
IdentityProviderService
/api/v1/inboxes
:
get
:
summary
:
ListInboxes lists inboxes for a user.
operationId
:
InboxService_ListInboxes
responses
:
"
200"
:
description
:
A successful response.
schema
:
$ref
:
'
#/definitions/v1ListInboxesResponse'
default
:
description
:
An unexpected error response.
schema
:
$ref
:
'
#/definitions/googlerpcStatus'
parameters
:
-
name
:
user
description
:
'
Format:
users/{user}'
in
:
query
required
:
false
type
:
string
-
name
:
pageSize
description
:
The maximum number of inbox to return.
in
:
query
required
:
false
type
:
integer
format
:
int32
-
name
:
pageToken
description
:
Provide this to retrieve the subsequent page.
in
:
query
required
:
false
type
:
string
tags
:
-
InboxService
/api/v1/markdown/link:metadata
:
get
:
summary
:
GetLinkMetadata returns metadata for a given link.
...
...
@@ -937,13 +905,14 @@ paths:
parameters
:
-
name
:
inbox.name
description
:
|-
The name of the inbox.
Format: inboxes/{i
d}, id is the system generated auto-incremented id.
The
resource
name of the inbox.
Format: inboxes/{i
nbox}
in: path
required: true
type: string
pattern: inboxes/[^/]+
-
name
:
inbox
description
:
Required. The inbox to update.
in
:
body
required
:
true
schema
:
...
...
@@ -951,20 +920,40 @@ paths:
properties
:
sender
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The sender of the inbox notification.
Format: users/{user}
readOnly: true
receiver
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The receiver of the inbox notification.
Format: users/{user}
readOnly: true
status
:
$ref
:
'
#/definitions/v1InboxStatus'
description
:
The status of the inbox notification.
createTime
:
type
:
string
format
:
date-time
description
:
Output only. The creation timestamp.
readOnly
:
true
type
:
$ref
:
'
#/definitions/v1InboxType'
description
:
The type of the inbox notification.
readOnly
:
true
activityId
:
type
:
integer
format
:
int32
description
:
Optional. The activity ID associated with this inbox notification.
title
:
Required. The inbox to update.
required
:
-
inbox
-
name
:
allowMissing
description
:
Optional. If set to
true
, allows updating missing fields.
in
:
query
required
:
false
type
:
boolean
tags
:
-
InboxService
/api/v1/{memo.name}
:
...
...
@@ -1263,7 +1252,9 @@ paths:
$ref
:
'
#/definitions/googlerpcStatus'
parameters
:
-
name
:
name_4
description
:
The name of the inbox to delete.
description
:
|-
Required. The resource name of the inbox to delete.
Format: inboxes/{inbox}
in: path
required: true
type: string
...
...
@@ -1810,6 +1801,63 @@ paths:
type
:
string
tags
:
-
UserService
/api/v1/{parent}/inboxes
:
get
:
summary
:
ListInboxes lists inboxes for a user.
operationId
:
InboxService_ListInboxes
responses
:
"
200"
:
description
:
A successful response.
schema
:
$ref
:
'
#/definitions/v1ListInboxesResponse'
default
:
description
:
An unexpected error response.
schema
:
$ref
:
'
#/definitions/googlerpcStatus'
parameters
:
-
name
:
parent
description
:
|-
Required. The parent resource whose inboxes will be listed.
Format: users/{user}
in: path
required: true
type: string
pattern: users/[^/]+
-
name
:
pageSize
description
:
|-
Optional. The maximum number of inboxes to return.
The service may return fewer than this value.
If unspecified, at most 50 inboxes will be returned.
The maximum value is 1000; values above 1000 will be coerced to 1000.
in: query
required: false
type: integer
format: int32
-
name
:
pageToken
description
:
|-
Optional. A page token, received from a previous `ListInboxes` call.
Provide this to retrieve the subsequent page.
in: query
required: false
type: string
-
name
:
filter
description
:
|-
Optional. Filter to apply to the list results.
Example: "status=UNREAD" or "type=MEMO_COMMENT"
Supported operators: =, !=
Supported fields: status, type, sender, create_time
in: query
required: false
type: string
-
name
:
orderBy
description
:
|-
Optional. The order to sort results by.
Example: "create_time desc" or "status asc"
in: query
required: false
type: string
tags
:
-
InboxService
/api/v1/{parent}/memos
:
get
:
summary
:
ListMemos lists memos with pagination and filter.
...
...
@@ -3170,25 +3218,37 @@ definitions:
properties
:
name
:
type
:
string
description
:
|-
The name of the inbox.
Format: inboxes/{i
d}, id is the system generated auto-incremented id.
title
:
|-
The
resource
name of the inbox.
Format: inboxes/{i
nbox}
sender
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The sender of the inbox notification.
Format: users/{user}
readOnly: true
receiver
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The receiver of the inbox notification.
Format: users/{user}
readOnly: true
status
:
$ref
:
'
#/definitions/v1InboxStatus'
description
:
The status of the inbox notification.
createTime
:
type
:
string
format
:
date-time
description
:
Output only. The creation timestamp.
readOnly
:
true
type
:
$ref
:
'
#/definitions/v1InboxType'
description
:
The type of the inbox notification.
readOnly
:
true
activityId
:
type
:
integer
format
:
int32
description
:
Optional. The activity ID associated with this inbox notification.
v1InboxStatus
:
type
:
string
enum
:
...
...
@@ -3196,6 +3256,12 @@ definitions:
-
UNREAD
-
ARCHIVED
default
:
STATUS_UNSPECIFIED
description
:
|-
Status enumeration for inbox notifications.
- STATUS_UNSPECIFIED: Unspecified status.
- UNREAD: The notification is unread.
- ARCHIVED: The notification is archived.
v1InboxType
:
type
:
string
enum
:
...
...
@@ -3203,6 +3269,12 @@ definitions:
-
MEMO_COMMENT
-
VERSION_UPDATE
default
:
TYPE_UNSPECIFIED
description
:
|-
Type enumeration for inbox notifications.
- TYPE_UNSPECIFIED: Unspecified type.
- MEMO_COMMENT: Memo comment notification.
- VERSION_UPDATE: Version update notification.
v1ItalicNode
:
type
:
object
properties
:
...
...
@@ -3303,11 +3375,16 @@ definitions:
items
:
type
:
object
$ref
:
'
#/definitions/v1Inbox'
description
:
The list of inboxes.
nextPageToken
:
type
:
string
description
:
|-
A token
, which
can be sent as `page_token` to retrieve the next page.
A token
that
can be sent as `page_token` to retrieve the next page.
If this field is omitted, there are no subsequent pages.
totalSize
:
type
:
integer
format
:
int32
description
:
The total count of inboxes (may be approximate).
v1ListMemoAttachmentsResponse
:
type
:
object
properties
:
...
...
server/router/api/v1/common.go
View file @
91c2a4ce
...
...
@@ -13,6 +13,8 @@ import (
const
(
// DefaultPageSize is the default page size for requests.
DefaultPageSize
=
10
// MaxPageSize is the maximum page size for requests.
MaxPageSize
=
1000
)
func
convertStateFromStore
(
rowStatus
store
.
RowStatus
)
v1pb
.
State
{
...
...
server/router/api/v1/inbox_service.go
View file @
91c2a4ce
...
...
@@ -15,9 +15,27 @@ import (
)
func
(
s
*
APIV1Service
)
ListInboxes
(
ctx
context
.
Context
,
request
*
v1pb
.
ListInboxesRequest
)
(
*
v1pb
.
ListInboxesResponse
,
error
)
{
user
,
err
:=
s
.
GetCurrentUser
(
ctx
)
// Extract user ID from parent resource name
userID
,
err
:=
ExtractUserIDFromName
(
request
.
Parent
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get user"
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid parent name %q: %v"
,
request
.
Parent
,
err
)
}
// Get current user for authorization
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user"
)
}
if
currentUser
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"user not authenticated"
)
}
// Check if current user can access the requested user's inboxes
if
currentUser
.
ID
!=
userID
{
// Only allow hosts and admins to access other users' inboxes
if
currentUser
.
Role
!=
store
.
RoleHost
&&
currentUser
.
Role
!=
store
.
RoleAdmin
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"cannot access inboxes for user %q"
,
request
.
Parent
)
}
}
var
limit
,
offset
int
...
...
@@ -34,15 +52,20 @@ func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxe
if
limit
<=
0
{
limit
=
DefaultPageSize
}
if
limit
>
MaxPageSize
{
limit
=
MaxPageSize
}
limitPlusOne
:=
limit
+
1
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ReceiverID
:
&
user
.
ID
,
findInbox
:=
&
store
.
FindInbox
{
ReceiverID
:
&
userID
,
Limit
:
&
limitPlusOne
,
Offset
:
&
offset
,
})
}
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
findInbox
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list inbox: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list inbox
es
: %v"
,
err
)
}
inboxMessages
:=
[]
*
v1pb
.
Inbox
{}
...
...
@@ -51,7 +74,7 @@ func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxe
inboxes
=
inboxes
[
:
limit
]
nextPageToken
,
err
=
getPageToken
(
limit
,
offset
+
limit
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get next page token
, error
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get next page token: %v"
,
err
)
}
}
for
_
,
inbox
:=
range
inboxes
{
...
...
@@ -65,6 +88,7 @@ func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxe
response
:=
&
v1pb
.
ListInboxesResponse
{
Inboxes
:
inboxMessages
,
NextPageToken
:
nextPageToken
,
TotalSize
:
int32
(
len
(
inboxMessages
)),
// For now, use actual returned count
}
return
response
,
nil
}
...
...
@@ -76,17 +100,46 @@ func (s *APIV1Service) UpdateInbox(ctx context.Context, request *v1pb.UpdateInbo
inboxID
,
err
:=
ExtractInboxIDFromName
(
request
.
Inbox
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name %q: %v"
,
request
.
Inbox
.
Name
,
err
)
}
// Get current user for authorization
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user"
)
}
if
currentUser
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"user not authenticated"
)
}
// Get the existing inbox to verify ownership
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ID
:
&
inboxID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get inbox: %v"
,
err
)
}
if
len
(
inboxes
)
==
0
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"inbox %q not found"
,
request
.
Inbox
.
Name
)
}
existingInbox
:=
inboxes
[
0
]
// Check if current user can update this inbox (must be the receiver)
if
currentUser
.
ID
!=
existingInbox
.
ReceiverID
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"cannot update inbox for another user"
)
}
update
:=
&
store
.
UpdateInbox
{
ID
:
inboxID
,
}
for
_
,
field
:=
range
request
.
UpdateMask
.
Paths
{
if
field
==
"status"
{
if
request
.
Inbox
.
Status
==
v1pb
.
Inbox_STATUS_UNSPECIFIED
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"status
is requir
ed"
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"status
cannot be unspecifi
ed"
)
}
update
.
Status
=
convertInboxStatusToStore
(
request
.
Inbox
.
Status
)
}
else
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"unsupported field in update mask: %q"
,
field
)
}
}
...
...
@@ -101,13 +154,39 @@ func (s *APIV1Service) UpdateInbox(ctx context.Context, request *v1pb.UpdateInbo
func
(
s
*
APIV1Service
)
DeleteInbox
(
ctx
context
.
Context
,
request
*
v1pb
.
DeleteInboxRequest
)
(
*
emptypb
.
Empty
,
error
)
{
inboxID
,
err
:=
ExtractInboxIDFromName
(
request
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name %q: %v"
,
request
.
Name
,
err
)
}
// Get current user for authorization
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user"
)
}
if
currentUser
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"user not authenticated"
)
}
// Get the existing inbox to verify ownership
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ID
:
&
inboxID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get inbox: %v"
,
err
)
}
if
len
(
inboxes
)
==
0
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"inbox %q not found"
,
request
.
Name
)
}
existingInbox
:=
inboxes
[
0
]
// Check if current user can delete this inbox (must be the receiver)
if
currentUser
.
ID
!=
existingInbox
.
ReceiverID
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"cannot delete inbox for another user"
)
}
if
err
:=
s
.
Store
.
DeleteInbox
(
ctx
,
&
store
.
DeleteInbox
{
ID
:
inboxID
,
});
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to
upda
te inbox: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to
dele
te inbox: %v"
,
err
)
}
return
&
emptypb
.
Empty
{},
nil
}
...
...
server/router/api/v1/test/inbox_service_test.go
0 → 100644
View file @
91c2a4ce
package
v1
import
(
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/fieldmaskpb"
v1pb
"github.com/usememos/memos/proto/gen/api/v1"
storepb
"github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store"
)
func
TestListInboxes
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
t
.
Run
(
"ListInboxes success"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
// List inboxes (should be empty initially)
req
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
fmt
.
Sprintf
(
"users/%d"
,
user
.
ID
),
}
resp
,
err
:=
ts
.
Service
.
ListInboxes
(
userCtx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
)
require
.
Empty
(
t
,
resp
.
Inboxes
)
require
.
Equal
(
t
,
int32
(
0
),
resp
.
TotalSize
)
})
t
.
Run
(
"ListInboxes with pagination"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Create some inbox entries
const
systemBotID
int32
=
0
for
i
:=
0
;
i
<
3
;
i
++
{
_
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
}
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
// List inboxes with page size limit
req
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
fmt
.
Sprintf
(
"users/%d"
,
user
.
ID
),
PageSize
:
2
,
}
resp
,
err
:=
ts
.
Service
.
ListInboxes
(
userCtx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
)
require
.
Equal
(
t
,
2
,
len
(
resp
.
Inboxes
))
require
.
NotEmpty
(
t
,
resp
.
NextPageToken
)
})
t
.
Run
(
"ListInboxes permission denied for different user"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create two users
user1
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"user1"
)
require
.
NoError
(
t
,
err
)
user2
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"user2"
)
require
.
NoError
(
t
,
err
)
// Set user1 context but try to list user2's inboxes
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user1
.
Username
)
req
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
fmt
.
Sprintf
(
"users/%d"
,
user2
.
ID
),
}
_
,
err
=
ts
.
Service
.
ListInboxes
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"cannot access inboxes"
)
})
t
.
Run
(
"ListInboxes host can access other users' inboxes"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a host user and a regular user
hostUser
,
err
:=
ts
.
CreateHostUser
(
ctx
,
"hostuser"
)
require
.
NoError
(
t
,
err
)
regularUser
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"regularuser"
)
require
.
NoError
(
t
,
err
)
// Create an inbox for the regular user
const
systemBotID
int32
=
0
_
,
err
=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
regularUser
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set host user context and try to list regular user's inboxes
hostCtx
:=
ts
.
CreateUserContext
(
ctx
,
hostUser
.
Username
)
req
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
fmt
.
Sprintf
(
"users/%d"
,
regularUser
.
ID
),
}
resp
,
err
:=
ts
.
Service
.
ListInboxes
(
hostCtx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
)
require
.
Equal
(
t
,
1
,
len
(
resp
.
Inboxes
))
})
t
.
Run
(
"ListInboxes invalid parent format"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
"invalid-parent-format"
,
}
_
,
err
=
ts
.
Service
.
ListInboxes
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"invalid parent name"
)
})
t
.
Run
(
"ListInboxes unauthenticated"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
req
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
"users/1"
,
}
_
,
err
:=
ts
.
Service
.
ListInboxes
(
ctx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"user not authenticated"
)
})
}
func
TestUpdateInbox
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
t
.
Run
(
"UpdateInbox success"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Create an inbox entry
const
systemBotID
int32
=
0
inbox
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
// Update inbox status
req
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
UpdateMask
:
&
fieldmaskpb
.
FieldMask
{
Paths
:
[]
string
{
"status"
},
},
}
resp
,
err
:=
ts
.
Service
.
UpdateInbox
(
userCtx
,
req
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
resp
)
require
.
Equal
(
t
,
v1pb
.
Inbox_ARCHIVED
,
resp
.
Status
)
})
t
.
Run
(
"UpdateInbox permission denied for different user"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create two users
user1
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"user1"
)
require
.
NoError
(
t
,
err
)
user2
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"user2"
)
require
.
NoError
(
t
,
err
)
// Create an inbox entry for user2
const
systemBotID
int32
=
0
inbox
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user2
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set user1 context but try to update user2's inbox
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user1
.
Username
)
req
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
UpdateMask
:
&
fieldmaskpb
.
FieldMask
{
Paths
:
[]
string
{
"status"
},
},
}
_
,
err
=
ts
.
Service
.
UpdateInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"cannot update inbox"
)
})
t
.
Run
(
"UpdateInbox missing update mask"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
"inboxes/1"
,
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
}
_
,
err
=
ts
.
Service
.
UpdateInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"update mask is required"
)
})
t
.
Run
(
"UpdateInbox invalid name format"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
"invalid-inbox-name"
,
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
UpdateMask
:
&
fieldmaskpb
.
FieldMask
{
Paths
:
[]
string
{
"status"
},
},
}
_
,
err
=
ts
.
Service
.
UpdateInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"invalid inbox name"
)
})
t
.
Run
(
"UpdateInbox not found"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
"inboxes/99999"
,
// Non-existent inbox
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
UpdateMask
:
&
fieldmaskpb
.
FieldMask
{
Paths
:
[]
string
{
"status"
},
},
}
_
,
err
=
ts
.
Service
.
UpdateInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
st
,
ok
:=
status
.
FromError
(
err
)
require
.
True
(
t
,
ok
)
require
.
Equal
(
t
,
codes
.
NotFound
,
st
.
Code
())
})
t
.
Run
(
"UpdateInbox unsupported field"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Create an inbox entry
const
systemBotID
int32
=
0
inbox
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
UpdateMask
:
&
fieldmaskpb
.
FieldMask
{
Paths
:
[]
string
{
"unsupported_field"
},
},
}
_
,
err
=
ts
.
Service
.
UpdateInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"unsupported field"
)
})
}
func
TestDeleteInbox
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
t
.
Run
(
"DeleteInbox success"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Create an inbox entry
const
systemBotID
int32
=
0
inbox
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
// Delete inbox
req
:=
&
v1pb
.
DeleteInboxRequest
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
}
_
,
err
=
ts
.
Service
.
DeleteInbox
(
userCtx
,
req
)
require
.
NoError
(
t
,
err
)
// Verify inbox is deleted
inboxes
,
err
:=
ts
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ReceiverID
:
&
user
.
ID
,
})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
0
,
len
(
inboxes
))
})
t
.
Run
(
"DeleteInbox permission denied for different user"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create two users
user1
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"user1"
)
require
.
NoError
(
t
,
err
)
user2
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"user2"
)
require
.
NoError
(
t
,
err
)
// Create an inbox entry for user2
const
systemBotID
int32
=
0
inbox
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user2
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set user1 context but try to delete user2's inbox
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user1
.
Username
)
req
:=
&
v1pb
.
DeleteInboxRequest
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
}
_
,
err
=
ts
.
Service
.
DeleteInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"cannot delete inbox"
)
})
t
.
Run
(
"DeleteInbox invalid name format"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
DeleteInboxRequest
{
Name
:
"invalid-inbox-name"
,
}
_
,
err
=
ts
.
Service
.
DeleteInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
require
.
Contains
(
t
,
err
.
Error
(),
"invalid inbox name"
)
})
t
.
Run
(
"DeleteInbox not found"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
req
:=
&
v1pb
.
DeleteInboxRequest
{
Name
:
"inboxes/99999"
,
// Non-existent inbox
}
_
,
err
=
ts
.
Service
.
DeleteInbox
(
userCtx
,
req
)
require
.
Error
(
t
,
err
)
st
,
ok
:=
status
.
FromError
(
err
)
require
.
True
(
t
,
ok
)
require
.
Equal
(
t
,
codes
.
NotFound
,
st
.
Code
())
})
}
func
TestInboxCRUDComplete
(
t
*
testing
.
T
)
{
ctx
:=
context
.
Background
()
t
.
Run
(
"Complete CRUD lifecycle"
,
func
(
t
*
testing
.
T
)
{
ts
:=
NewTestService
(
t
)
defer
ts
.
Cleanup
()
// Create a user
user
,
err
:=
ts
.
CreateRegularUser
(
ctx
,
"testuser"
)
require
.
NoError
(
t
,
err
)
// Create an inbox entry directly in store
const
systemBotID
int32
=
0
inbox
,
err
:=
ts
.
Store
.
CreateInbox
(
ctx
,
&
store
.
Inbox
{
SenderID
:
systemBotID
,
ReceiverID
:
user
.
ID
,
Status
:
store
.
UNREAD
,
Message
:
&
storepb
.
InboxMessage
{
Type
:
storepb
.
InboxMessage_MEMO_COMMENT
,
},
})
require
.
NoError
(
t
,
err
)
// Set user context
userCtx
:=
ts
.
CreateUserContext
(
ctx
,
user
.
Username
)
// 1. List inboxes - should have 1
listReq
:=
&
v1pb
.
ListInboxesRequest
{
Parent
:
fmt
.
Sprintf
(
"users/%d"
,
user
.
ID
),
}
listResp
,
err
:=
ts
.
Service
.
ListInboxes
(
userCtx
,
listReq
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
1
,
len
(
listResp
.
Inboxes
))
require
.
Equal
(
t
,
v1pb
.
Inbox_UNREAD
,
listResp
.
Inboxes
[
0
]
.
Status
)
// 2. Update inbox status to ARCHIVED
updateReq
:=
&
v1pb
.
UpdateInboxRequest
{
Inbox
:
&
v1pb
.
Inbox
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
Status
:
v1pb
.
Inbox_ARCHIVED
,
},
UpdateMask
:
&
fieldmaskpb
.
FieldMask
{
Paths
:
[]
string
{
"status"
},
},
}
updateResp
,
err
:=
ts
.
Service
.
UpdateInbox
(
userCtx
,
updateReq
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
v1pb
.
Inbox_ARCHIVED
,
updateResp
.
Status
)
// 3. List inboxes again - should still have 1 but ARCHIVED
listResp
,
err
=
ts
.
Service
.
ListInboxes
(
userCtx
,
listReq
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
1
,
len
(
listResp
.
Inboxes
))
require
.
Equal
(
t
,
v1pb
.
Inbox_ARCHIVED
,
listResp
.
Inboxes
[
0
]
.
Status
)
// 4. Delete inbox
deleteReq
:=
&
v1pb
.
DeleteInboxRequest
{
Name
:
fmt
.
Sprintf
(
"inboxes/%d"
,
inbox
.
ID
),
}
_
,
err
=
ts
.
Service
.
DeleteInbox
(
userCtx
,
deleteReq
)
require
.
NoError
(
t
,
err
)
// 5. List inboxes - should be empty
listResp
,
err
=
ts
.
Service
.
ListInboxes
(
userCtx
,
listReq
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
0
,
len
(
listResp
.
Inboxes
))
require
.
Equal
(
t
,
int32
(
0
),
listResp
.
TotalSize
)
})
}
web/src/pages/Inboxes.tsx
View file @
91c2a4ce
...
...
@@ -13,14 +13,23 @@ import { useTranslate } from "@/utils/i18n";
const
Inboxes
=
observer
(()
=>
{
const
t
=
useTranslate
();
const
{
md
}
=
useResponsiveWidth
();
const
inboxes
=
sortBy
(
userStore
.
state
.
inboxes
,
(
inbox
)
=>
{
if
(
inbox
.
status
===
Inbox_Status
.
UNREAD
)
return
0
;
if
(
inbox
.
status
===
Inbox_Status
.
ARCHIVED
)
return
1
;
return
2
;
});
const
fetchInboxes
=
async
()
=>
{
try
{
await
userStore
.
fetchInboxes
();
}
catch
(
error
)
{
console
.
error
(
"Failed to fetch inboxes:"
,
error
);
}
};
useEffect
(()
=>
{
userStore
.
fetchInboxes
();
fetchInboxes
();
},
[]);
return
(
...
...
web/src/store/v2/user.ts
View file @
91c2a4ce
...
...
@@ -160,7 +160,14 @@ const userStore = (() => {
};
const
fetchInboxes
=
async
()
=>
{
const
{
inboxes
}
=
await
inboxServiceClient
.
listInboxes
({});
if
(
!
state
.
currentUser
)
{
throw
new
Error
(
"No current user available"
);
}
const
{
inboxes
}
=
await
inboxServiceClient
.
listInboxes
({
parent
:
state
.
currentUser
,
});
state
.
setPartial
({
inboxes
,
});
...
...
web/src/types/proto/api/v1/inbox_service.ts
View file @
91c2a4ce
...
...
@@ -14,23 +14,39 @@ export const protobufPackage = "memos.api.v1";
export
interface
Inbox
{
/**
* The name of the inbox.
* Format: inboxes/{i
d}, id is the system generated auto-incremented id.
* The
resource
name of the inbox.
* Format: inboxes/{i
nbox}
*/
name
:
string
;
/** Format: users/{user} */
/**
* The sender of the inbox notification.
* Format: users/{user}
*/
sender
:
string
;
/** Format: users/{user} */
/**
* The receiver of the inbox notification.
* Format: users/{user}
*/
receiver
:
string
;
/** The status of the inbox notification. */
status
:
Inbox_Status
;
createTime
?:
Date
|
undefined
;
/** Output only. The creation timestamp. */
createTime
?:
|
Date
|
undefined
;
/** The type of the inbox notification. */
type
:
Inbox_Type
;
/** Optional. The activity ID associated with this inbox notification. */
activityId
?:
number
|
undefined
;
}
/** Status enumeration for inbox notifications. */
export
enum
Inbox_Status
{
/** STATUS_UNSPECIFIED - Unspecified status. */
STATUS_UNSPECIFIED
=
"STATUS_UNSPECIFIED"
,
/** UNREAD - The notification is unread. */
UNREAD
=
"UNREAD"
,
/** ARCHIVED - The notification is archived. */
ARCHIVED
=
"ARCHIVED"
,
UNRECOGNIZED
=
"UNRECOGNIZED"
,
}
...
...
@@ -67,9 +83,13 @@ export function inbox_StatusToNumber(object: Inbox_Status): number {
}
}
/** Type enumeration for inbox notifications. */
export
enum
Inbox_Type
{
/** TYPE_UNSPECIFIED - Unspecified type. */
TYPE_UNSPECIFIED
=
"TYPE_UNSPECIFIED"
,
/** MEMO_COMMENT - Memo comment notification. */
MEMO_COMMENT
=
"MEMO_COMMENT"
,
/** VERSION_UPDATE - Version update notification. */
VERSION_UPDATE
=
"VERSION_UPDATE"
,
UNRECOGNIZED
=
"UNRECOGNIZED"
,
}
...
...
@@ -107,30 +127,67 @@ export function inbox_TypeToNumber(object: Inbox_Type): number {
}
export
interface
ListInboxesRequest
{
/** Format: users/{user} */
user
:
string
;
/** The maximum number of inbox to return. */
/**
* Required. The parent resource whose inboxes will be listed.
* Format: users/{user}
*/
parent
:
string
;
/**
* Optional. The maximum number of inboxes to return.
* The service may return fewer than this value.
* If unspecified, at most 50 inboxes will be returned.
* The maximum value is 1000; values above 1000 will be coerced to 1000.
*/
pageSize
:
number
;
/** Provide this to retrieve the subsequent page. */
/**
* Optional. A page token, received from a previous `ListInboxes` call.
* Provide this to retrieve the subsequent page.
*/
pageToken
:
string
;
/**
* Optional. Filter to apply to the list results.
* Example: "status=UNREAD" or "type=MEMO_COMMENT"
* Supported operators: =, !=
* Supported fields: status, type, sender, create_time
*/
filter
:
string
;
/**
* Optional. The order to sort results by.
* Example: "create_time desc" or "status asc"
*/
orderBy
:
string
;
}
export
interface
ListInboxesResponse
{
/** The list of inboxes. */
inboxes
:
Inbox
[];
/**
* A token
, which
can be sent as `page_token` to retrieve the next page.
* A token
that
can be sent as `page_token` to retrieve the next page.
* If this field is omitted, there are no subsequent pages.
*/
nextPageToken
:
string
;
/** The total count of inboxes (may be approximate). */
totalSize
:
number
;
}
export
interface
UpdateInboxRequest
{
inbox
?:
Inbox
|
undefined
;
updateMask
?:
string
[]
|
undefined
;
/** Required. The inbox to update. */
inbox
?:
|
Inbox
|
undefined
;
/** Required. The list of fields to update. */
updateMask
?:
|
string
[]
|
undefined
;
/** Optional. If set to true, allows updating missing fields. */
allowMissing
:
boolean
;
}
export
interface
DeleteInboxRequest
{
/** The name of the inbox to delete. */
/**
* Required. The resource name of the inbox to delete.
* Format: inboxes/{inbox}
*/
name
:
string
;
}
...
...
@@ -261,13 +318,13 @@ export const Inbox: MessageFns<Inbox> = {
};
function
createBaseListInboxesRequest
():
ListInboxesRequest
{
return
{
user
:
""
,
pageSize
:
0
,
pageToken
:
""
};
return
{
parent
:
""
,
pageSize
:
0
,
pageToken
:
""
,
filter
:
""
,
orderBy
:
""
};
}
export
const
ListInboxesRequest
:
MessageFns
<
ListInboxesRequest
>
=
{
encode
(
message
:
ListInboxesRequest
,
writer
:
BinaryWriter
=
new
BinaryWriter
()):
BinaryWriter
{
if
(
message
.
user
!==
""
)
{
writer
.
uint32
(
10
).
string
(
message
.
user
);
if
(
message
.
parent
!==
""
)
{
writer
.
uint32
(
10
).
string
(
message
.
parent
);
}
if
(
message
.
pageSize
!==
0
)
{
writer
.
uint32
(
16
).
int32
(
message
.
pageSize
);
...
...
@@ -275,6 +332,12 @@ export const ListInboxesRequest: MessageFns<ListInboxesRequest> = {
if
(
message
.
pageToken
!==
""
)
{
writer
.
uint32
(
26
).
string
(
message
.
pageToken
);
}
if
(
message
.
filter
!==
""
)
{
writer
.
uint32
(
34
).
string
(
message
.
filter
);
}
if
(
message
.
orderBy
!==
""
)
{
writer
.
uint32
(
42
).
string
(
message
.
orderBy
);
}
return
writer
;
},
...
...
@@ -290,7 +353,7 @@ export const ListInboxesRequest: MessageFns<ListInboxesRequest> = {
break
;
}
message
.
user
=
reader
.
string
();
message
.
parent
=
reader
.
string
();
continue
;
}
case
2
:
{
...
...
@@ -309,6 +372,22 @@ export const ListInboxesRequest: MessageFns<ListInboxesRequest> = {
message
.
pageToken
=
reader
.
string
();
continue
;
}
case
4
:
{
if
(
tag
!==
34
)
{
break
;
}
message
.
filter
=
reader
.
string
();
continue
;
}
case
5
:
{
if
(
tag
!==
42
)
{
break
;
}
message
.
orderBy
=
reader
.
string
();
continue
;
}
}
if
((
tag
&
7
)
===
4
||
tag
===
0
)
{
break
;
...
...
@@ -323,15 +402,17 @@ export const ListInboxesRequest: MessageFns<ListInboxesRequest> = {
},
fromPartial
(
object
:
DeepPartial
<
ListInboxesRequest
>
):
ListInboxesRequest
{
const
message
=
createBaseListInboxesRequest
();
message
.
user
=
object
.
user
??
""
;
message
.
parent
=
object
.
parent
??
""
;
message
.
pageSize
=
object
.
pageSize
??
0
;
message
.
pageToken
=
object
.
pageToken
??
""
;
message
.
filter
=
object
.
filter
??
""
;
message
.
orderBy
=
object
.
orderBy
??
""
;
return
message
;
},
};
function
createBaseListInboxesResponse
():
ListInboxesResponse
{
return
{
inboxes
:
[],
nextPageToken
:
""
};
return
{
inboxes
:
[],
nextPageToken
:
""
,
totalSize
:
0
};
}
export
const
ListInboxesResponse
:
MessageFns
<
ListInboxesResponse
>
=
{
...
...
@@ -342,6 +423,9 @@ export const ListInboxesResponse: MessageFns<ListInboxesResponse> = {
if
(
message
.
nextPageToken
!==
""
)
{
writer
.
uint32
(
18
).
string
(
message
.
nextPageToken
);
}
if
(
message
.
totalSize
!==
0
)
{
writer
.
uint32
(
24
).
int32
(
message
.
totalSize
);
}
return
writer
;
},
...
...
@@ -368,6 +452,14 @@ export const ListInboxesResponse: MessageFns<ListInboxesResponse> = {
message
.
nextPageToken
=
reader
.
string
();
continue
;
}
case
3
:
{
if
(
tag
!==
24
)
{
break
;
}
message
.
totalSize
=
reader
.
int32
();
continue
;
}
}
if
((
tag
&
7
)
===
4
||
tag
===
0
)
{
break
;
...
...
@@ -384,12 +476,13 @@ export const ListInboxesResponse: MessageFns<ListInboxesResponse> = {
const
message
=
createBaseListInboxesResponse
();
message
.
inboxes
=
object
.
inboxes
?.
map
((
e
)
=>
Inbox
.
fromPartial
(
e
))
||
[];
message
.
nextPageToken
=
object
.
nextPageToken
??
""
;
message
.
totalSize
=
object
.
totalSize
??
0
;
return
message
;
},
};
function
createBaseUpdateInboxRequest
():
UpdateInboxRequest
{
return
{
inbox
:
undefined
,
updateMask
:
undefined
};
return
{
inbox
:
undefined
,
updateMask
:
undefined
,
allowMissing
:
false
};
}
export
const
UpdateInboxRequest
:
MessageFns
<
UpdateInboxRequest
>
=
{
...
...
@@ -400,6 +493,9 @@ export const UpdateInboxRequest: MessageFns<UpdateInboxRequest> = {
if
(
message
.
updateMask
!==
undefined
)
{
FieldMask
.
encode
(
FieldMask
.
wrap
(
message
.
updateMask
),
writer
.
uint32
(
18
).
fork
()).
join
();
}
if
(
message
.
allowMissing
!==
false
)
{
writer
.
uint32
(
24
).
bool
(
message
.
allowMissing
);
}
return
writer
;
},
...
...
@@ -426,6 +522,14 @@ export const UpdateInboxRequest: MessageFns<UpdateInboxRequest> = {
message
.
updateMask
=
FieldMask
.
unwrap
(
FieldMask
.
decode
(
reader
,
reader
.
uint32
()));
continue
;
}
case
3
:
{
if
(
tag
!==
24
)
{
break
;
}
message
.
allowMissing
=
reader
.
bool
();
continue
;
}
}
if
((
tag
&
7
)
===
4
||
tag
===
0
)
{
break
;
...
...
@@ -442,6 +546,7 @@ export const UpdateInboxRequest: MessageFns<UpdateInboxRequest> = {
const
message
=
createBaseUpdateInboxRequest
();
message
.
inbox
=
(
object
.
inbox
!==
undefined
&&
object
.
inbox
!==
null
)
?
Inbox
.
fromPartial
(
object
.
inbox
)
:
undefined
;
message
.
updateMask
=
object
.
updateMask
??
undefined
;
message
.
allowMissing
=
object
.
allowMissing
??
false
;
return
message
;
},
};
...
...
@@ -506,8 +611,45 @@ export const InboxServiceDefinition = {
responseStream
:
false
,
options
:
{
_unknownFields
:
{
8410
:
[
new
Uint8Array
([
6
,
112
,
97
,
114
,
101
,
110
,
116
])],
578365826
:
[
new
Uint8Array
([
17
,
18
,
15
,
47
,
97
,
112
,
105
,
47
,
118
,
49
,
47
,
105
,
110
,
98
,
111
,
120
,
101
,
115
]),
new
Uint8Array
([
34
,
18
,
32
,
47
,
97
,
112
,
105
,
47
,
118
,
49
,
47
,
123
,
112
,
97
,
114
,
101
,
110
,
116
,
61
,
117
,
115
,
101
,
114
,
115
,
47
,
42
,
125
,
47
,
105
,
110
,
98
,
111
,
120
,
101
,
115
,
]),
],
},
},
...
...
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