mirror of
https://github.com/usememos/memos.git
synced 2025-10-25 05:46:03 +08:00
refactor: inbox service
This commit is contained in:
parent
a4920d464b
commit
91c2a4cef9
10 changed files with 1176 additions and 152 deletions
|
|
@ -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;
|
||||
// Format: users/{user}
|
||||
string sender = 2;
|
||||
// Format: users/{user}
|
||||
string receiver = 3;
|
||||
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 [(google.api.field_behavior) = OUTPUT_ONLY];
|
||||
|
||||
// The receiver of the inbox notification.
|
||||
// Format: users/{user}
|
||||
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"}
|
||||
];
|
||||
|
||||
// The maximum number of inbox to return.
|
||||
int32 page_size = 2;
|
||||
// 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;
|
||||
string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
|
||||
|
||||
// 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];
|
||||
|
||||
// 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];
|
||||
|
||||
google.protobuf.FieldMask update_mask = 2;
|
||||
// Required. The list of fields to update.
|
||||
google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED];
|
||||
|
||||
// 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"}
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/{id}, id is the system generated auto-incremented id.
|
||||
// The resource name of the inbox.
|
||||
// Format: inboxes/{inbox}
|
||||
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) GetUser() string {
|
||||
func (x *ListInboxesRequest) GetParent() 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" +
|
||||
"\x1aapi/v1/inbox_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xa4\x03\n" +
|
||||
"\x05Inbox\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" +
|
||||
"\x06sender\x18\x02 \x01(\tR\x06sender\x12\x1a\n" +
|
||||
"\breceiver\x18\x03 \x01(\tR\breceiver\x122\n" +
|
||||
"\x06status\x18\x04 \x01(\x0e2\x1a.memos.api.v1.Inbox.StatusR\x06status\x12;\n" +
|
||||
"\vcreate_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\n" +
|
||||
"createTime\x12,\n" +
|
||||
"\x04type\x18\x06 \x01(\x0e2\x18.memos.api.v1.Inbox.TypeR\x04type\x12$\n" +
|
||||
"\vactivity_id\x18\a \x01(\x05H\x00R\n" +
|
||||
"\x1aapi/v1/inbox_service.proto\x12\fmemos.api.v1\x1a\x1cgoogle/api/annotations.proto\x1a\x17google/api/client.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\x87\x04\n" +
|
||||
"\x05Inbox\x12\x17\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12\x1b\n" +
|
||||
"\x06sender\x18\x02 \x01(\tB\x03\xe0A\x03R\x06sender\x12\x1f\n" +
|
||||
"\breceiver\x18\x03 \x01(\tB\x03\xe0A\x03R\breceiver\x127\n" +
|
||||
"\x06status\x18\x04 \x01(\x0e2\x1a.memos.api.v1.Inbox.StatusB\x03\xe0A\x01R\x06status\x12@\n" +
|
||||
"\vcreate_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
|
||||
"createTime\x121\n" +
|
||||
"\x04type\x18\x06 \x01(\x0e2\x18.memos.api.v1.Inbox.TypeB\x03\xe0A\x03R\x04type\x12)\n" +
|
||||
"\vactivity_id\x18\a \x01(\x05B\x03\xe0A\x01H\x00R\n" +
|
||||
"activityId\x88\x01\x01\":\n" +
|
||||
"\x06Status\x12\x16\n" +
|
||||
"\x12STATUS_UNSPECIFIED\x10\x00\x12\n" +
|
||||
|
|
@ -456,24 +519,32 @@ const file_api_v1_inbox_service_proto_rawDesc = "" +
|
|||
"\x04Type\x12\x14\n" +
|
||||
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
|
||||
"\fMEMO_COMMENT\x10\x01\x12\x12\n" +
|
||||
"\x0eVERSION_UPDATE\x10\x02B\x0e\n" +
|
||||
"\f_activity_id\"d\n" +
|
||||
"\x12ListInboxesRequest\x12\x12\n" +
|
||||
"\x04user\x18\x01 \x01(\tR\x04user\x12\x1b\n" +
|
||||
"\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" +
|
||||
"\x0eVERSION_UPDATE\x10\x02:>\xeaA;\n" +
|
||||
"\x12memos.api.v1/Inbox\x12\x0finboxes/{inbox}\x1a\x04name*\ainboxes2\x05inboxB\x0e\n" +
|
||||
"\f_activity_id\"\xca\x01\n" +
|
||||
"\x12ListInboxesRequest\x121\n" +
|
||||
"\x06parent\x18\x01 \x01(\tB\x19\xe0A\x02\xfaA\x13\n" +
|
||||
"\x11memos.api.v1/UserR\x06parent\x12 \n" +
|
||||
"\tpage_size\x18\x02 \x01(\x05B\x03\xe0A\x01R\bpageSize\x12\"\n" +
|
||||
"\n" +
|
||||
"page_token\x18\x03 \x01(\tR\tpageToken\"l\n" +
|
||||
"page_token\x18\x03 \x01(\tB\x03\xe0A\x01R\tpageToken\x12\x1b\n" +
|
||||
"\x06filter\x18\x04 \x01(\tB\x03\xe0A\x01R\x06filter\x12\x1e\n" +
|
||||
"\border_by\x18\x05 \x01(\tB\x03\xe0A\x01R\aorderBy\"\x8b\x01\n" +
|
||||
"\x13ListInboxesResponse\x12-\n" +
|
||||
"\ainboxes\x18\x01 \x03(\v2\x13.memos.api.v1.InboxR\ainboxes\x12&\n" +
|
||||
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"|\n" +
|
||||
"\x12UpdateInboxRequest\x12)\n" +
|
||||
"\x05inbox\x18\x01 \x01(\v2\x13.memos.api.v1.InboxR\x05inbox\x12;\n" +
|
||||
"\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskR\n" +
|
||||
"updateMask\"(\n" +
|
||||
"\x12DeleteInboxRequest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name2\xf7\x02\n" +
|
||||
"\fInboxService\x12k\n" +
|
||||
"\vListInboxes\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" +
|
||||
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\x12\x1d\n" +
|
||||
"\n" +
|
||||
"total_size\x18\x03 \x01(\x05R\ttotalSize\"\xb0\x01\n" +
|
||||
"\x12UpdateInboxRequest\x12.\n" +
|
||||
"\x05inbox\x18\x01 \x01(\v2\x13.memos.api.v1.InboxB\x03\xe0A\x02R\x05inbox\x12@\n" +
|
||||
"\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskB\x03\xe0A\x02R\n" +
|
||||
"updateMask\x12(\n" +
|
||||
"\rallow_missing\x18\x03 \x01(\bB\x03\xe0A\x01R\fallowMissing\"D\n" +
|
||||
"\x12DeleteInboxRequest\x12.\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x1a\xe0A\x02\xfaA\x14\n" +
|
||||
"\x12memos.api.v1/InboxR\x04name2\x92\x03\n" +
|
||||
"\fInboxService\x12\x85\x01\n" +
|
||||
"\vListInboxes\x12 .memos.api.v1.ListInboxesRequest\x1a!.memos.api.v1.ListInboxesResponse\"1\xdaA\x06parent\x82\xd3\xe4\x93\x02\"\x12 /api/v1/{parent=users/*}/inboxes\x12\x87\x01\n" +
|
||||
"\vUpdateInbox\x12 .memos.api.v1.UpdateInboxRequest\x1a\x13.memos.api.v1.Inbox\"A\xdaA\x11inbox,update_mask\x82\xd3\xe4\x93\x02':\x05inbox2\x1e/api/v1/{inbox.name=inboxes/*}\x12p\n" +
|
||||
"\vDeleteInbox\x12 .memos.api.v1.DeleteInboxRequest\x1a\x16.google.protobuf.Empty\"'\xdaA\x04name\x82\xd3\xe4\x93\x02\x1a*\x18/api/v1/{name=inboxes/*}B\xa9\x01\n" +
|
||||
"\x10com.memos.api.v1B\x11InboxServiceProtoP\x01Z0github.com/usememos/memos/proto/gen/api/v1;apiv1\xa2\x02\x03MAX\xaa\x02\fMemos.Api.V1\xca\x02\fMemos\\Api\\V1\xe2\x02\x18Memos\\Api\\V1\\GPBMetadata\xea\x02\x0eMemos::Api::V1b\x06proto3"
|
||||
|
|
|
|||
|
|
@ -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"}, ""))
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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/{id}, id is the system generated auto-incremented id.
|
||||
The resource name of the inbox.
|
||||
Format: inboxes/{inbox}
|
||||
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/{id}, id is the system generated auto-incremented id.
|
||||
title: |-
|
||||
The resource name of the inbox.
|
||||
Format: inboxes/{inbox}
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 inboxes: %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 required")
|
||||
return nil, status.Errorf(codes.InvalidArgument, "status cannot be unspecified")
|
||||
}
|
||||
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 update inbox: %v", err)
|
||||
return nil, status.Errorf(codes.Internal, "failed to delete inbox: %v", err)
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
|
|
|||
559
server/router/api/v1/test/inbox_service_test.go
Normal file
559
server/router/api/v1/test/inbox_service_test.go
Normal file
|
|
@ -0,0 +1,559 @@
|
|||
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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,23 +14,39 @@ export const protobufPackage = "memos.api.v1";
|
|||
|
||||
export interface Inbox {
|
||||
/**
|
||||
* The name of the inbox.
|
||||
* Format: inboxes/{id}, id is the system generated auto-incremented id.
|
||||
* The resource name of the inbox.
|
||||
* Format: inboxes/{inbox}
|
||||
*/
|
||||
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,
|
||||
]),
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue