refactor(api): migrate inbox functionality to user notifications

- Remove standalone InboxService and move functionality to UserService
- Rename inbox to user notifications for better API consistency
- Add ListUserNotifications, UpdateUserNotification, DeleteUserNotification methods
- Update frontend components to use new notification endpoints
- Update store layer to support new notification model

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
pull/5075/merge
Steven 2 days ago
parent e915e3a46b
commit bc1550e926

@ -61,8 +61,6 @@ message Activity {
TYPE_UNSPECIFIED = 0;
// Memo comment activity.
MEMO_COMMENT = 1;
// Version update activity.
VERSION_UPDATE = 2;
}
// Activity levels.

@ -1,149 +0,0 @@
syntax = "proto3";
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";
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/{parent=users/*}/inboxes"};
option (google.api.method_signature) = "parent";
}
// UpdateInbox updates an inbox.
rpc UpdateInbox(UpdateInboxRequest) returns (Inbox) {
option (google.api.http) = {
patch: "/api/v1/{inbox.name=inboxes/*}"
body: "inbox"
};
option (google.api.method_signature) = "inbox,update_mask";
}
// DeleteInbox deletes an inbox.
rpc DeleteInbox(DeleteInboxRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{name=inboxes/*}"};
option (google.api.method_signature) = "name";
}
}
message Inbox {
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;
}
// Type enumeration for inbox notifications.
enum Type {
// Unspecified type.
TYPE_UNSPECIFIED = 0;
// Memo comment notification.
MEMO_COMMENT = 1;
// Version update notification.
VERSION_UPDATE = 2;
}
}
message ListInboxesRequest {
// Required. The parent resource whose inboxes will be listed.
// Format: users/{user}
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];
// 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 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 {
// 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];
// Optional. If set to true, allows updating missing fields.
bool allow_missing = 3 [(google.api.field_behavior) = OPTIONAL];
}
message DeleteInboxRequest {
// 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"}
];
}

@ -153,6 +153,27 @@ service UserService {
option (google.api.http) = {delete: "/api/v1/{name=users/*/webhooks/*}"};
option (google.api.method_signature) = "name";
}
// ListUserNotifications lists notifications for a user.
rpc ListUserNotifications(ListUserNotificationsRequest) returns (ListUserNotificationsResponse) {
option (google.api.http) = {get: "/api/v1/{parent=users/*}/notifications"};
option (google.api.method_signature) = "parent";
}
// UpdateUserNotification updates a notification.
rpc UpdateUserNotification(UpdateUserNotificationRequest) returns (UserNotification) {
option (google.api.http) = {
patch: "/api/v1/{notification.name=users/*/notifications/*}"
body: "notification"
};
option (google.api.method_signature) = "notification,update_mask";
}
// DeleteUserNotification deletes a notification.
rpc DeleteUserNotification(DeleteUserNotificationRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{name=users/*/notifications/*}"};
option (google.api.method_signature) = "name";
}
}
message User {
@ -672,3 +693,81 @@ message DeleteUserWebhookRequest {
// Format: users/{user}/webhooks/{webhook}
string name = 1 [(google.api.field_behavior) = REQUIRED];
}
message UserNotification {
option (google.api.resource) = {
type: "memos.api.v1/UserNotification"
pattern: "users/{user}/notifications/{notification}"
name_field: "name"
singular: "notification"
plural: "notifications"
};
// The resource name of the notification.
// Format: users/{user}/notifications/{notification}
string name = 1 [
(google.api.field_behavior) = OUTPUT_ONLY,
(google.api.field_behavior) = IDENTIFIER
];
// The sender of the notification.
// Format: users/{user}
string sender = 2 [
(google.api.field_behavior) = OUTPUT_ONLY,
(google.api.resource_reference) = {type: "memos.api.v1/User"}
];
// The status of the notification.
Status status = 3 [(google.api.field_behavior) = OPTIONAL];
// The creation timestamp.
google.protobuf.Timestamp create_time = 4 [(google.api.field_behavior) = OUTPUT_ONLY];
// The type of the notification.
Type type = 5 [(google.api.field_behavior) = OUTPUT_ONLY];
// The activity ID associated with this notification.
optional int32 activity_id = 6 [(google.api.field_behavior) = OPTIONAL];
enum Status {
STATUS_UNSPECIFIED = 0;
UNREAD = 1;
ARCHIVED = 2;
}
enum Type {
TYPE_UNSPECIFIED = 0;
MEMO_COMMENT = 1;
}
}
message ListUserNotificationsRequest {
// The parent user resource.
// Format: users/{user}
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/User"}
];
int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];
string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
string filter = 4 [(google.api.field_behavior) = OPTIONAL];
}
message ListUserNotificationsResponse {
repeated UserNotification notifications = 1;
string next_page_token = 2;
}
message UpdateUserNotificationRequest {
UserNotification notification = 1 [(google.api.field_behavior) = REQUIRED];
google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = REQUIRED];
}
message DeleteUserNotificationRequest {
// Format: users/{user}/notifications/{notification}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/UserNotification"}
];
}

@ -31,8 +31,6 @@ const (
Activity_TYPE_UNSPECIFIED Activity_Type = 0
// Memo comment activity.
Activity_MEMO_COMMENT Activity_Type = 1
// Version update activity.
Activity_VERSION_UPDATE Activity_Type = 2
)
// Enum value maps for Activity_Type.
@ -40,12 +38,10 @@ var (
Activity_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED",
1: "MEMO_COMMENT",
2: "VERSION_UPDATE",
}
Activity_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0,
"MEMO_COMMENT": 1,
"VERSION_UPDATE": 2,
}
)
@ -513,7 +509,7 @@ var File_api_v1_activity_service_proto protoreflect.FileDescriptor
const file_api_v1_activity_service_proto_rawDesc = "" +
"\n" +
"\x1dapi/v1/activity_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\x1fgoogle/protobuf/timestamp.proto\"\x86\x04\n" +
"\x1dapi/v1/activity_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\x1fgoogle/protobuf/timestamp.proto\"\xf2\x03\n" +
"\bActivity\x12\x1a\n" +
"\x04name\x18\x01 \x01(\tB\x06\xe0A\x03\xe0A\bR\x04name\x12\x1d\n" +
"\acreator\x18\x02 \x01(\tB\x03\xe0A\x03R\acreator\x124\n" +
@ -521,11 +517,10 @@ const file_api_v1_activity_service_proto_rawDesc = "" +
"\x05level\x18\x04 \x01(\x0e2\x1c.memos.api.v1.Activity.LevelB\x03\xe0A\x03R\x05level\x12@\n" +
"\vcreate_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
"createTime\x12<\n" +
"\apayload\x18\x06 \x01(\v2\x1d.memos.api.v1.ActivityPayloadB\x03\xe0A\x03R\apayload\"B\n" +
"\apayload\x18\x06 \x01(\v2\x1d.memos.api.v1.ActivityPayloadB\x03\xe0A\x03R\apayload\".\n" +
"\x04Type\x12\x14\n" +
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
"\fMEMO_COMMENT\x10\x01\x12\x12\n" +
"\x0eVERSION_UPDATE\x10\x02\"=\n" +
"\fMEMO_COMMENT\x10\x01\"=\n" +
"\x05Level\x12\x15\n" +
"\x11LEVEL_UNSPECIFIED\x10\x00\x12\b\n" +
"\x04INFO\x10\x01\x12\b\n" +

@ -86,8 +86,6 @@ const (
Inbox_TYPE_UNSPECIFIED Inbox_Type = 0
// Memo comment notification.
Inbox_MEMO_COMMENT Inbox_Type = 1
// Version update notification.
Inbox_VERSION_UPDATE Inbox_Type = 2
)
// Enum value maps for Inbox_Type.
@ -95,12 +93,10 @@ var (
Inbox_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED",
1: "MEMO_COMMENT",
2: "VERSION_UPDATE",
}
Inbox_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0,
"MEMO_COMMENT": 1,
"VERSION_UPDATE": 2,
}
)
@ -500,7 +496,7 @@ 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\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" +
"\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\"\xf3\x03\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" +
@ -515,11 +511,10 @@ const file_api_v1_inbox_service_proto_rawDesc = "" +
"\x12STATUS_UNSPECIFIED\x10\x00\x12\n" +
"\n" +
"\x06UNREAD\x10\x01\x12\f\n" +
"\bARCHIVED\x10\x02\"B\n" +
"\bARCHIVED\x10\x02\".\n" +
"\x04Type\x12\x14\n" +
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
"\fMEMO_COMMENT\x10\x01\x12\x12\n" +
"\x0eVERSION_UPDATE\x10\x02:>\xeaA;\n" +
"\fMEMO_COMMENT\x10\x01:>\xeaA;\n" +
"\x12memos.api.v1/Inbox\x12\x0finboxes/{inbox}\x1a\x04name*\ainboxes2\x05inboxB\x0e\n" +
"\f_activity_id\"\xca\x01\n" +
"\x12ListInboxesRequest\x121\n" +

@ -143,6 +143,101 @@ func (UserSetting_Key) EnumDescriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{12, 0}
}
type UserNotification_Status int32
const (
UserNotification_STATUS_UNSPECIFIED UserNotification_Status = 0
UserNotification_UNREAD UserNotification_Status = 1
UserNotification_ARCHIVED UserNotification_Status = 2
)
// Enum value maps for UserNotification_Status.
var (
UserNotification_Status_name = map[int32]string{
0: "STATUS_UNSPECIFIED",
1: "UNREAD",
2: "ARCHIVED",
}
UserNotification_Status_value = map[string]int32{
"STATUS_UNSPECIFIED": 0,
"UNREAD": 1,
"ARCHIVED": 2,
}
)
func (x UserNotification_Status) Enum() *UserNotification_Status {
p := new(UserNotification_Status)
*p = x
return p
}
func (x UserNotification_Status) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UserNotification_Status) Descriptor() protoreflect.EnumDescriptor {
return file_api_v1_user_service_proto_enumTypes[2].Descriptor()
}
func (UserNotification_Status) Type() protoreflect.EnumType {
return &file_api_v1_user_service_proto_enumTypes[2]
}
func (x UserNotification_Status) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UserNotification_Status.Descriptor instead.
func (UserNotification_Status) EnumDescriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{32, 0}
}
type UserNotification_Type int32
const (
UserNotification_TYPE_UNSPECIFIED UserNotification_Type = 0
UserNotification_MEMO_COMMENT UserNotification_Type = 1
)
// Enum value maps for UserNotification_Type.
var (
UserNotification_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED",
1: "MEMO_COMMENT",
}
UserNotification_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0,
"MEMO_COMMENT": 1,
}
)
func (x UserNotification_Type) Enum() *UserNotification_Type {
p := new(UserNotification_Type)
*p = x
return p
}
func (x UserNotification_Type) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (UserNotification_Type) Descriptor() protoreflect.EnumDescriptor {
return file_api_v1_user_service_proto_enumTypes[3].Descriptor()
}
func (UserNotification_Type) Type() protoreflect.EnumType {
return &file_api_v1_user_service_proto_enumTypes[3]
}
func (x UserNotification_Type) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use UserNotification_Type.Descriptor instead.
func (UserNotification_Type) EnumDescriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{32, 1}
}
type User struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The resource name of the user.
@ -2169,6 +2264,317 @@ func (x *DeleteUserWebhookRequest) GetName() string {
return ""
}
type UserNotification struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The resource name of the notification.
// Format: users/{user}/notifications/{notification}
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
// The sender of the notification.
// Format: users/{user}
Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"`
// The status of the notification.
Status UserNotification_Status `protobuf:"varint,3,opt,name=status,proto3,enum=memos.api.v1.UserNotification_Status" json:"status,omitempty"`
// The creation timestamp.
CreateTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty"`
// The type of the notification.
Type UserNotification_Type `protobuf:"varint,5,opt,name=type,proto3,enum=memos.api.v1.UserNotification_Type" json:"type,omitempty"`
// The activity ID associated with this notification.
ActivityId *int32 `protobuf:"varint,6,opt,name=activity_id,json=activityId,proto3,oneof" json:"activity_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UserNotification) Reset() {
*x = UserNotification{}
mi := &file_api_v1_user_service_proto_msgTypes[32]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UserNotification) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UserNotification) ProtoMessage() {}
func (x *UserNotification) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[32]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UserNotification.ProtoReflect.Descriptor instead.
func (*UserNotification) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{32}
}
func (x *UserNotification) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *UserNotification) GetSender() string {
if x != nil {
return x.Sender
}
return ""
}
func (x *UserNotification) GetStatus() UserNotification_Status {
if x != nil {
return x.Status
}
return UserNotification_STATUS_UNSPECIFIED
}
func (x *UserNotification) GetCreateTime() *timestamppb.Timestamp {
if x != nil {
return x.CreateTime
}
return nil
}
func (x *UserNotification) GetType() UserNotification_Type {
if x != nil {
return x.Type
}
return UserNotification_TYPE_UNSPECIFIED
}
func (x *UserNotification) GetActivityId() int32 {
if x != nil && x.ActivityId != nil {
return *x.ActivityId
}
return 0
}
type ListUserNotificationsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The parent user resource.
// Format: users/{user}
Parent string `protobuf:"bytes,1,opt,name=parent,proto3" json:"parent,omitempty"`
PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
PageToken string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"`
Filter string `protobuf:"bytes,4,opt,name=filter,proto3" json:"filter,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListUserNotificationsRequest) Reset() {
*x = ListUserNotificationsRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListUserNotificationsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUserNotificationsRequest) ProtoMessage() {}
func (x *ListUserNotificationsRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[33]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUserNotificationsRequest.ProtoReflect.Descriptor instead.
func (*ListUserNotificationsRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{33}
}
func (x *ListUserNotificationsRequest) GetParent() string {
if x != nil {
return x.Parent
}
return ""
}
func (x *ListUserNotificationsRequest) GetPageSize() int32 {
if x != nil {
return x.PageSize
}
return 0
}
func (x *ListUserNotificationsRequest) GetPageToken() string {
if x != nil {
return x.PageToken
}
return ""
}
func (x *ListUserNotificationsRequest) GetFilter() string {
if x != nil {
return x.Filter
}
return ""
}
type ListUserNotificationsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Notifications []*UserNotification `protobuf:"bytes,1,rep,name=notifications,proto3" json:"notifications,omitempty"`
NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListUserNotificationsResponse) Reset() {
*x = ListUserNotificationsResponse{}
mi := &file_api_v1_user_service_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListUserNotificationsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListUserNotificationsResponse) ProtoMessage() {}
func (x *ListUserNotificationsResponse) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ListUserNotificationsResponse.ProtoReflect.Descriptor instead.
func (*ListUserNotificationsResponse) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{34}
}
func (x *ListUserNotificationsResponse) GetNotifications() []*UserNotification {
if x != nil {
return x.Notifications
}
return nil
}
func (x *ListUserNotificationsResponse) GetNextPageToken() string {
if x != nil {
return x.NextPageToken
}
return ""
}
type UpdateUserNotificationRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Notification *UserNotification `protobuf:"bytes,1,opt,name=notification,proto3" json:"notification,omitempty"`
UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,2,opt,name=update_mask,json=updateMask,proto3" json:"update_mask,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateUserNotificationRequest) Reset() {
*x = UpdateUserNotificationRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateUserNotificationRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateUserNotificationRequest) ProtoMessage() {}
func (x *UpdateUserNotificationRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use UpdateUserNotificationRequest.ProtoReflect.Descriptor instead.
func (*UpdateUserNotificationRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{35}
}
func (x *UpdateUserNotificationRequest) GetNotification() *UserNotification {
if x != nil {
return x.Notification
}
return nil
}
func (x *UpdateUserNotificationRequest) GetUpdateMask() *fieldmaskpb.FieldMask {
if x != nil {
return x.UpdateMask
}
return nil
}
type DeleteUserNotificationRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Format: users/{user}/notifications/{notification}
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeleteUserNotificationRequest) Reset() {
*x = DeleteUserNotificationRequest{}
mi := &file_api_v1_user_service_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeleteUserNotificationRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteUserNotificationRequest) ProtoMessage() {}
func (x *DeleteUserNotificationRequest) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DeleteUserNotificationRequest.ProtoReflect.Descriptor instead.
func (*DeleteUserNotificationRequest) Descriptor() ([]byte, []int) {
return file_api_v1_user_service_proto_rawDescGZIP(), []int{36}
}
func (x *DeleteUserNotificationRequest) GetName() string {
if x != nil {
return x.Name
}
return ""
}
// Memo type statistics.
type UserStats_MemoTypeStats struct {
state protoimpl.MessageState `protogen:"open.v1"`
@ -2182,7 +2588,7 @@ type UserStats_MemoTypeStats struct {
func (x *UserStats_MemoTypeStats) Reset() {
*x = UserStats_MemoTypeStats{}
mi := &file_api_v1_user_service_proto_msgTypes[33]
mi := &file_api_v1_user_service_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2194,7 +2600,7 @@ func (x *UserStats_MemoTypeStats) String() string {
func (*UserStats_MemoTypeStats) ProtoMessage() {}
func (x *UserStats_MemoTypeStats) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[33]
mi := &file_api_v1_user_service_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2255,7 +2661,7 @@ type UserSetting_GeneralSetting struct {
func (x *UserSetting_GeneralSetting) Reset() {
*x = UserSetting_GeneralSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[34]
mi := &file_api_v1_user_service_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2267,7 +2673,7 @@ func (x *UserSetting_GeneralSetting) String() string {
func (*UserSetting_GeneralSetting) ProtoMessage() {}
func (x *UserSetting_GeneralSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[34]
mi := &file_api_v1_user_service_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2315,7 +2721,7 @@ type UserSetting_SessionsSetting struct {
func (x *UserSetting_SessionsSetting) Reset() {
*x = UserSetting_SessionsSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[35]
mi := &file_api_v1_user_service_proto_msgTypes[40]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2327,7 +2733,7 @@ func (x *UserSetting_SessionsSetting) String() string {
func (*UserSetting_SessionsSetting) ProtoMessage() {}
func (x *UserSetting_SessionsSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[35]
mi := &file_api_v1_user_service_proto_msgTypes[40]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2361,7 +2767,7 @@ type UserSetting_AccessTokensSetting struct {
func (x *UserSetting_AccessTokensSetting) Reset() {
*x = UserSetting_AccessTokensSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[36]
mi := &file_api_v1_user_service_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2373,7 +2779,7 @@ func (x *UserSetting_AccessTokensSetting) String() string {
func (*UserSetting_AccessTokensSetting) ProtoMessage() {}
func (x *UserSetting_AccessTokensSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[36]
mi := &file_api_v1_user_service_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2407,7 +2813,7 @@ type UserSetting_WebhooksSetting struct {
func (x *UserSetting_WebhooksSetting) Reset() {
*x = UserSetting_WebhooksSetting{}
mi := &file_api_v1_user_service_proto_msgTypes[37]
mi := &file_api_v1_user_service_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2419,7 +2825,7 @@ func (x *UserSetting_WebhooksSetting) String() string {
func (*UserSetting_WebhooksSetting) ProtoMessage() {}
func (x *UserSetting_WebhooksSetting) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[37]
mi := &file_api_v1_user_service_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2460,7 +2866,7 @@ type UserSession_ClientInfo struct {
func (x *UserSession_ClientInfo) Reset() {
*x = UserSession_ClientInfo{}
mi := &file_api_v1_user_service_proto_msgTypes[38]
mi := &file_api_v1_user_service_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
@ -2472,7 +2878,7 @@ func (x *UserSession_ClientInfo) String() string {
func (*UserSession_ClientInfo) ProtoMessage() {}
func (x *UserSession_ClientInfo) ProtoReflect() protoreflect.Message {
mi := &file_api_v1_user_service_proto_msgTypes[38]
mi := &file_api_v1_user_service_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@ -2725,7 +3131,44 @@ const file_api_v1_user_service_proto_rawDesc = "" +
"\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskR\n" +
"updateMask\"3\n" +
"\x18DeleteUserWebhookRequest\x12\x17\n" +
"\x04name\x18\x01 \x01(\tB\x03\xe0A\x02R\x04name2\xe6\x15\n" +
"\x04name\x18\x01 \x01(\tB\x03\xe0A\x02R\x04name\"\xbe\x04\n" +
"\x10UserNotification\x12\x1a\n" +
"\x04name\x18\x01 \x01(\tB\x06\xe0A\x03\xe0A\bR\x04name\x121\n" +
"\x06sender\x18\x02 \x01(\tB\x19\xe0A\x03\xfaA\x13\n" +
"\x11memos.api.v1/UserR\x06sender\x12B\n" +
"\x06status\x18\x03 \x01(\x0e2%.memos.api.v1.UserNotification.StatusB\x03\xe0A\x01R\x06status\x12@\n" +
"\vcreate_time\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
"createTime\x12<\n" +
"\x04type\x18\x05 \x01(\x0e2#.memos.api.v1.UserNotification.TypeB\x03\xe0A\x03R\x04type\x12)\n" +
"\vactivity_id\x18\x06 \x01(\x05B\x03\xe0A\x01H\x00R\n" +
"activityId\x88\x01\x01\":\n" +
"\x06Status\x12\x16\n" +
"\x12STATUS_UNSPECIFIED\x10\x00\x12\n" +
"\n" +
"\x06UNREAD\x10\x01\x12\f\n" +
"\bARCHIVED\x10\x02\".\n" +
"\x04Type\x12\x14\n" +
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
"\fMEMO_COMMENT\x10\x01:p\xeaAm\n" +
"\x1dmemos.api.v1/UserNotification\x12)users/{user}/notifications/{notification}\x1a\x04name*\rnotifications2\fnotificationB\x0e\n" +
"\f_activity_id\"\xb4\x01\n" +
"\x1cListUserNotificationsRequest\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(\tB\x03\xe0A\x01R\tpageToken\x12\x1b\n" +
"\x06filter\x18\x04 \x01(\tB\x03\xe0A\x01R\x06filter\"\x8d\x01\n" +
"\x1dListUserNotificationsResponse\x12D\n" +
"\rnotifications\x18\x01 \x03(\v2\x1e.memos.api.v1.UserNotificationR\rnotifications\x12&\n" +
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"\xaa\x01\n" +
"\x1dUpdateUserNotificationRequest\x12G\n" +
"\fnotification\x18\x01 \x01(\v2\x1e.memos.api.v1.UserNotificationB\x03\xe0A\x02R\fnotification\x12@\n" +
"\vupdate_mask\x18\x02 \x01(\v2\x1a.google.protobuf.FieldMaskB\x03\xe0A\x02R\n" +
"updateMask\"Z\n" +
"\x1dDeleteUserNotificationRequest\x129\n" +
"\x04name\x18\x01 \x01(\tB%\xe0A\x02\xfaA\x1f\n" +
"\x1dmemos.api.v1/UserNotificationR\x04name2\xf7\x19\n" +
"\vUserService\x12c\n" +
"\tListUsers\x12\x1e.memos.api.v1.ListUsersRequest\x1a\x1f.memos.api.v1.ListUsersResponse\"\x15\x82\xd3\xe4\x93\x02\x0f\x12\r/api/v1/users\x12b\n" +
"\aGetUser\x12\x1c.memos.api.v1.GetUserRequest\x1a\x12.memos.api.v1.User\"%\xdaA\x04name\x82\xd3\xe4\x93\x02\x18\x12\x16/api/v1/{name=users/*}\x12e\n" +
@ -2749,7 +3192,10 @@ const file_api_v1_user_service_proto_rawDesc = "" +
"\x10ListUserWebhooks\x12%.memos.api.v1.ListUserWebhooksRequest\x1a&.memos.api.v1.ListUserWebhooksResponse\"2\xdaA\x06parent\x82\xd3\xe4\x93\x02#\x12!/api/v1/{parent=users/*}/webhooks\x12\x9b\x01\n" +
"\x11CreateUserWebhook\x12&.memos.api.v1.CreateUserWebhookRequest\x1a\x19.memos.api.v1.UserWebhook\"C\xdaA\x0eparent,webhook\x82\xd3\xe4\x93\x02,:\awebhook\"!/api/v1/{parent=users/*}/webhooks\x12\xa8\x01\n" +
"\x11UpdateUserWebhook\x12&.memos.api.v1.UpdateUserWebhookRequest\x1a\x19.memos.api.v1.UserWebhook\"P\xdaA\x13webhook,update_mask\x82\xd3\xe4\x93\x024:\awebhook2)/api/v1/{webhook.name=users/*/webhooks/*}\x12\x85\x01\n" +
"\x11DeleteUserWebhook\x12&.memos.api.v1.DeleteUserWebhookRequest\x1a\x16.google.protobuf.Empty\"0\xdaA\x04name\x82\xd3\xe4\x93\x02#*!/api/v1/{name=users/*/webhooks/*}B\xa8\x01\n" +
"\x11DeleteUserWebhook\x12&.memos.api.v1.DeleteUserWebhookRequest\x1a\x16.google.protobuf.Empty\"0\xdaA\x04name\x82\xd3\xe4\x93\x02#*!/api/v1/{name=users/*/webhooks/*}\x12\xa9\x01\n" +
"\x15ListUserNotifications\x12*.memos.api.v1.ListUserNotificationsRequest\x1a+.memos.api.v1.ListUserNotificationsResponse\"7\xdaA\x06parent\x82\xd3\xe4\x93\x02(\x12&/api/v1/{parent=users/*}/notifications\x12\xcb\x01\n" +
"\x16UpdateUserNotification\x12+.memos.api.v1.UpdateUserNotificationRequest\x1a\x1e.memos.api.v1.UserNotification\"d\xdaA\x18notification,update_mask\x82\xd3\xe4\x93\x02C:\fnotification23/api/v1/{notification.name=users/*/notifications/*}\x12\x94\x01\n" +
"\x16DeleteUserNotification\x12+.memos.api.v1.DeleteUserNotificationRequest\x1a\x16.google.protobuf.Empty\"5\xdaA\x04name\x82\xd3\xe4\x93\x02(*&/api/v1/{name=users/*/notifications/*}B\xa8\x01\n" +
"\x10com.memos.api.v1B\x10UserServiceProtoP\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"
var (
@ -2764,139 +3210,158 @@ func file_api_v1_user_service_proto_rawDescGZIP() []byte {
return file_api_v1_user_service_proto_rawDescData
}
var file_api_v1_user_service_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_api_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 39)
var file_api_v1_user_service_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
var file_api_v1_user_service_proto_msgTypes = make([]protoimpl.MessageInfo, 44)
var file_api_v1_user_service_proto_goTypes = []any{
(User_Role)(0), // 0: memos.api.v1.User.Role
(UserSetting_Key)(0), // 1: memos.api.v1.UserSetting.Key
(*User)(nil), // 2: memos.api.v1.User
(*ListUsersRequest)(nil), // 3: memos.api.v1.ListUsersRequest
(*ListUsersResponse)(nil), // 4: memos.api.v1.ListUsersResponse
(*GetUserRequest)(nil), // 5: memos.api.v1.GetUserRequest
(*CreateUserRequest)(nil), // 6: memos.api.v1.CreateUserRequest
(*UpdateUserRequest)(nil), // 7: memos.api.v1.UpdateUserRequest
(*DeleteUserRequest)(nil), // 8: memos.api.v1.DeleteUserRequest
(*GetUserAvatarRequest)(nil), // 9: memos.api.v1.GetUserAvatarRequest
(*UserStats)(nil), // 10: memos.api.v1.UserStats
(*GetUserStatsRequest)(nil), // 11: memos.api.v1.GetUserStatsRequest
(*ListAllUserStatsRequest)(nil), // 12: memos.api.v1.ListAllUserStatsRequest
(*ListAllUserStatsResponse)(nil), // 13: memos.api.v1.ListAllUserStatsResponse
(*UserSetting)(nil), // 14: memos.api.v1.UserSetting
(*GetUserSettingRequest)(nil), // 15: memos.api.v1.GetUserSettingRequest
(*UpdateUserSettingRequest)(nil), // 16: memos.api.v1.UpdateUserSettingRequest
(*ListUserSettingsRequest)(nil), // 17: memos.api.v1.ListUserSettingsRequest
(*ListUserSettingsResponse)(nil), // 18: memos.api.v1.ListUserSettingsResponse
(*UserAccessToken)(nil), // 19: memos.api.v1.UserAccessToken
(*ListUserAccessTokensRequest)(nil), // 20: memos.api.v1.ListUserAccessTokensRequest
(*ListUserAccessTokensResponse)(nil), // 21: memos.api.v1.ListUserAccessTokensResponse
(*CreateUserAccessTokenRequest)(nil), // 22: memos.api.v1.CreateUserAccessTokenRequest
(*DeleteUserAccessTokenRequest)(nil), // 23: memos.api.v1.DeleteUserAccessTokenRequest
(*UserSession)(nil), // 24: memos.api.v1.UserSession
(*ListUserSessionsRequest)(nil), // 25: memos.api.v1.ListUserSessionsRequest
(*ListUserSessionsResponse)(nil), // 26: memos.api.v1.ListUserSessionsResponse
(*RevokeUserSessionRequest)(nil), // 27: memos.api.v1.RevokeUserSessionRequest
(*UserWebhook)(nil), // 28: memos.api.v1.UserWebhook
(*ListUserWebhooksRequest)(nil), // 29: memos.api.v1.ListUserWebhooksRequest
(*ListUserWebhooksResponse)(nil), // 30: memos.api.v1.ListUserWebhooksResponse
(*CreateUserWebhookRequest)(nil), // 31: memos.api.v1.CreateUserWebhookRequest
(*UpdateUserWebhookRequest)(nil), // 32: memos.api.v1.UpdateUserWebhookRequest
(*DeleteUserWebhookRequest)(nil), // 33: memos.api.v1.DeleteUserWebhookRequest
nil, // 34: memos.api.v1.UserStats.TagCountEntry
(*UserStats_MemoTypeStats)(nil), // 35: memos.api.v1.UserStats.MemoTypeStats
(*UserSetting_GeneralSetting)(nil), // 36: memos.api.v1.UserSetting.GeneralSetting
(*UserSetting_SessionsSetting)(nil), // 37: memos.api.v1.UserSetting.SessionsSetting
(*UserSetting_AccessTokensSetting)(nil), // 38: memos.api.v1.UserSetting.AccessTokensSetting
(*UserSetting_WebhooksSetting)(nil), // 39: memos.api.v1.UserSetting.WebhooksSetting
(*UserSession_ClientInfo)(nil), // 40: memos.api.v1.UserSession.ClientInfo
(State)(0), // 41: memos.api.v1.State
(*timestamppb.Timestamp)(nil), // 42: google.protobuf.Timestamp
(*fieldmaskpb.FieldMask)(nil), // 43: google.protobuf.FieldMask
(*emptypb.Empty)(nil), // 44: google.protobuf.Empty
(*httpbody.HttpBody)(nil), // 45: google.api.HttpBody
(UserNotification_Status)(0), // 2: memos.api.v1.UserNotification.Status
(UserNotification_Type)(0), // 3: memos.api.v1.UserNotification.Type
(*User)(nil), // 4: memos.api.v1.User
(*ListUsersRequest)(nil), // 5: memos.api.v1.ListUsersRequest
(*ListUsersResponse)(nil), // 6: memos.api.v1.ListUsersResponse
(*GetUserRequest)(nil), // 7: memos.api.v1.GetUserRequest
(*CreateUserRequest)(nil), // 8: memos.api.v1.CreateUserRequest
(*UpdateUserRequest)(nil), // 9: memos.api.v1.UpdateUserRequest
(*DeleteUserRequest)(nil), // 10: memos.api.v1.DeleteUserRequest
(*GetUserAvatarRequest)(nil), // 11: memos.api.v1.GetUserAvatarRequest
(*UserStats)(nil), // 12: memos.api.v1.UserStats
(*GetUserStatsRequest)(nil), // 13: memos.api.v1.GetUserStatsRequest
(*ListAllUserStatsRequest)(nil), // 14: memos.api.v1.ListAllUserStatsRequest
(*ListAllUserStatsResponse)(nil), // 15: memos.api.v1.ListAllUserStatsResponse
(*UserSetting)(nil), // 16: memos.api.v1.UserSetting
(*GetUserSettingRequest)(nil), // 17: memos.api.v1.GetUserSettingRequest
(*UpdateUserSettingRequest)(nil), // 18: memos.api.v1.UpdateUserSettingRequest
(*ListUserSettingsRequest)(nil), // 19: memos.api.v1.ListUserSettingsRequest
(*ListUserSettingsResponse)(nil), // 20: memos.api.v1.ListUserSettingsResponse
(*UserAccessToken)(nil), // 21: memos.api.v1.UserAccessToken
(*ListUserAccessTokensRequest)(nil), // 22: memos.api.v1.ListUserAccessTokensRequest
(*ListUserAccessTokensResponse)(nil), // 23: memos.api.v1.ListUserAccessTokensResponse
(*CreateUserAccessTokenRequest)(nil), // 24: memos.api.v1.CreateUserAccessTokenRequest
(*DeleteUserAccessTokenRequest)(nil), // 25: memos.api.v1.DeleteUserAccessTokenRequest
(*UserSession)(nil), // 26: memos.api.v1.UserSession
(*ListUserSessionsRequest)(nil), // 27: memos.api.v1.ListUserSessionsRequest
(*ListUserSessionsResponse)(nil), // 28: memos.api.v1.ListUserSessionsResponse
(*RevokeUserSessionRequest)(nil), // 29: memos.api.v1.RevokeUserSessionRequest
(*UserWebhook)(nil), // 30: memos.api.v1.UserWebhook
(*ListUserWebhooksRequest)(nil), // 31: memos.api.v1.ListUserWebhooksRequest
(*ListUserWebhooksResponse)(nil), // 32: memos.api.v1.ListUserWebhooksResponse
(*CreateUserWebhookRequest)(nil), // 33: memos.api.v1.CreateUserWebhookRequest
(*UpdateUserWebhookRequest)(nil), // 34: memos.api.v1.UpdateUserWebhookRequest
(*DeleteUserWebhookRequest)(nil), // 35: memos.api.v1.DeleteUserWebhookRequest
(*UserNotification)(nil), // 36: memos.api.v1.UserNotification
(*ListUserNotificationsRequest)(nil), // 37: memos.api.v1.ListUserNotificationsRequest
(*ListUserNotificationsResponse)(nil), // 38: memos.api.v1.ListUserNotificationsResponse
(*UpdateUserNotificationRequest)(nil), // 39: memos.api.v1.UpdateUserNotificationRequest
(*DeleteUserNotificationRequest)(nil), // 40: memos.api.v1.DeleteUserNotificationRequest
nil, // 41: memos.api.v1.UserStats.TagCountEntry
(*UserStats_MemoTypeStats)(nil), // 42: memos.api.v1.UserStats.MemoTypeStats
(*UserSetting_GeneralSetting)(nil), // 43: memos.api.v1.UserSetting.GeneralSetting
(*UserSetting_SessionsSetting)(nil), // 44: memos.api.v1.UserSetting.SessionsSetting
(*UserSetting_AccessTokensSetting)(nil), // 45: memos.api.v1.UserSetting.AccessTokensSetting
(*UserSetting_WebhooksSetting)(nil), // 46: memos.api.v1.UserSetting.WebhooksSetting
(*UserSession_ClientInfo)(nil), // 47: memos.api.v1.UserSession.ClientInfo
(State)(0), // 48: memos.api.v1.State
(*timestamppb.Timestamp)(nil), // 49: google.protobuf.Timestamp
(*fieldmaskpb.FieldMask)(nil), // 50: google.protobuf.FieldMask
(*emptypb.Empty)(nil), // 51: google.protobuf.Empty
(*httpbody.HttpBody)(nil), // 52: google.api.HttpBody
}
var file_api_v1_user_service_proto_depIdxs = []int32{
0, // 0: memos.api.v1.User.role:type_name -> memos.api.v1.User.Role
41, // 1: memos.api.v1.User.state:type_name -> memos.api.v1.State
42, // 2: memos.api.v1.User.create_time:type_name -> google.protobuf.Timestamp
42, // 3: memos.api.v1.User.update_time:type_name -> google.protobuf.Timestamp
2, // 4: memos.api.v1.ListUsersResponse.users:type_name -> memos.api.v1.User
43, // 5: memos.api.v1.GetUserRequest.read_mask:type_name -> google.protobuf.FieldMask
2, // 6: memos.api.v1.CreateUserRequest.user:type_name -> memos.api.v1.User
2, // 7: memos.api.v1.UpdateUserRequest.user:type_name -> memos.api.v1.User
43, // 8: memos.api.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask
42, // 9: memos.api.v1.UserStats.memo_display_timestamps:type_name -> google.protobuf.Timestamp
35, // 10: memos.api.v1.UserStats.memo_type_stats:type_name -> memos.api.v1.UserStats.MemoTypeStats
34, // 11: memos.api.v1.UserStats.tag_count:type_name -> memos.api.v1.UserStats.TagCountEntry
10, // 12: memos.api.v1.ListAllUserStatsResponse.stats:type_name -> memos.api.v1.UserStats
36, // 13: memos.api.v1.UserSetting.general_setting:type_name -> memos.api.v1.UserSetting.GeneralSetting
37, // 14: memos.api.v1.UserSetting.sessions_setting:type_name -> memos.api.v1.UserSetting.SessionsSetting
38, // 15: memos.api.v1.UserSetting.access_tokens_setting:type_name -> memos.api.v1.UserSetting.AccessTokensSetting
39, // 16: memos.api.v1.UserSetting.webhooks_setting:type_name -> memos.api.v1.UserSetting.WebhooksSetting
14, // 17: memos.api.v1.UpdateUserSettingRequest.setting:type_name -> memos.api.v1.UserSetting
43, // 18: memos.api.v1.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask
14, // 19: memos.api.v1.ListUserSettingsResponse.settings:type_name -> memos.api.v1.UserSetting
42, // 20: memos.api.v1.UserAccessToken.issued_at:type_name -> google.protobuf.Timestamp
42, // 21: memos.api.v1.UserAccessToken.expires_at:type_name -> google.protobuf.Timestamp
19, // 22: memos.api.v1.ListUserAccessTokensResponse.access_tokens:type_name -> memos.api.v1.UserAccessToken
19, // 23: memos.api.v1.CreateUserAccessTokenRequest.access_token:type_name -> memos.api.v1.UserAccessToken
42, // 24: memos.api.v1.UserSession.create_time:type_name -> google.protobuf.Timestamp
42, // 25: memos.api.v1.UserSession.last_accessed_time:type_name -> google.protobuf.Timestamp
40, // 26: memos.api.v1.UserSession.client_info:type_name -> memos.api.v1.UserSession.ClientInfo
24, // 27: memos.api.v1.ListUserSessionsResponse.sessions:type_name -> memos.api.v1.UserSession
42, // 28: memos.api.v1.UserWebhook.create_time:type_name -> google.protobuf.Timestamp
42, // 29: memos.api.v1.UserWebhook.update_time:type_name -> google.protobuf.Timestamp
28, // 30: memos.api.v1.ListUserWebhooksResponse.webhooks:type_name -> memos.api.v1.UserWebhook
28, // 31: memos.api.v1.CreateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook
28, // 32: memos.api.v1.UpdateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook
43, // 33: memos.api.v1.UpdateUserWebhookRequest.update_mask:type_name -> google.protobuf.FieldMask
24, // 34: memos.api.v1.UserSetting.SessionsSetting.sessions:type_name -> memos.api.v1.UserSession
19, // 35: memos.api.v1.UserSetting.AccessTokensSetting.access_tokens:type_name -> memos.api.v1.UserAccessToken
28, // 36: memos.api.v1.UserSetting.WebhooksSetting.webhooks:type_name -> memos.api.v1.UserWebhook
3, // 37: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest
5, // 38: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest
6, // 39: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest
7, // 40: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest
8, // 41: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest
9, // 42: memos.api.v1.UserService.GetUserAvatar:input_type -> memos.api.v1.GetUserAvatarRequest
12, // 43: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest
11, // 44: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest
15, // 45: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest
16, // 46: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest
17, // 47: memos.api.v1.UserService.ListUserSettings:input_type -> memos.api.v1.ListUserSettingsRequest
20, // 48: memos.api.v1.UserService.ListUserAccessTokens:input_type -> memos.api.v1.ListUserAccessTokensRequest
22, // 49: memos.api.v1.UserService.CreateUserAccessToken:input_type -> memos.api.v1.CreateUserAccessTokenRequest
23, // 50: memos.api.v1.UserService.DeleteUserAccessToken:input_type -> memos.api.v1.DeleteUserAccessTokenRequest
25, // 51: memos.api.v1.UserService.ListUserSessions:input_type -> memos.api.v1.ListUserSessionsRequest
27, // 52: memos.api.v1.UserService.RevokeUserSession:input_type -> memos.api.v1.RevokeUserSessionRequest
29, // 53: memos.api.v1.UserService.ListUserWebhooks:input_type -> memos.api.v1.ListUserWebhooksRequest
31, // 54: memos.api.v1.UserService.CreateUserWebhook:input_type -> memos.api.v1.CreateUserWebhookRequest
32, // 55: memos.api.v1.UserService.UpdateUserWebhook:input_type -> memos.api.v1.UpdateUserWebhookRequest
33, // 56: memos.api.v1.UserService.DeleteUserWebhook:input_type -> memos.api.v1.DeleteUserWebhookRequest
4, // 57: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse
2, // 58: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User
2, // 59: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User
2, // 60: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User
44, // 61: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty
45, // 62: memos.api.v1.UserService.GetUserAvatar:output_type -> google.api.HttpBody
13, // 63: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse
10, // 64: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats
14, // 65: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting
14, // 66: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting
18, // 67: memos.api.v1.UserService.ListUserSettings:output_type -> memos.api.v1.ListUserSettingsResponse
21, // 68: memos.api.v1.UserService.ListUserAccessTokens:output_type -> memos.api.v1.ListUserAccessTokensResponse
19, // 69: memos.api.v1.UserService.CreateUserAccessToken:output_type -> memos.api.v1.UserAccessToken
44, // 70: memos.api.v1.UserService.DeleteUserAccessToken:output_type -> google.protobuf.Empty
26, // 71: memos.api.v1.UserService.ListUserSessions:output_type -> memos.api.v1.ListUserSessionsResponse
44, // 72: memos.api.v1.UserService.RevokeUserSession:output_type -> google.protobuf.Empty
30, // 73: memos.api.v1.UserService.ListUserWebhooks:output_type -> memos.api.v1.ListUserWebhooksResponse
28, // 74: memos.api.v1.UserService.CreateUserWebhook:output_type -> memos.api.v1.UserWebhook
28, // 75: memos.api.v1.UserService.UpdateUserWebhook:output_type -> memos.api.v1.UserWebhook
44, // 76: memos.api.v1.UserService.DeleteUserWebhook:output_type -> google.protobuf.Empty
57, // [57:77] is the sub-list for method output_type
37, // [37:57] is the sub-list for method input_type
37, // [37:37] is the sub-list for extension type_name
37, // [37:37] is the sub-list for extension extendee
0, // [0:37] is the sub-list for field type_name
48, // 1: memos.api.v1.User.state:type_name -> memos.api.v1.State
49, // 2: memos.api.v1.User.create_time:type_name -> google.protobuf.Timestamp
49, // 3: memos.api.v1.User.update_time:type_name -> google.protobuf.Timestamp
4, // 4: memos.api.v1.ListUsersResponse.users:type_name -> memos.api.v1.User
50, // 5: memos.api.v1.GetUserRequest.read_mask:type_name -> google.protobuf.FieldMask
4, // 6: memos.api.v1.CreateUserRequest.user:type_name -> memos.api.v1.User
4, // 7: memos.api.v1.UpdateUserRequest.user:type_name -> memos.api.v1.User
50, // 8: memos.api.v1.UpdateUserRequest.update_mask:type_name -> google.protobuf.FieldMask
49, // 9: memos.api.v1.UserStats.memo_display_timestamps:type_name -> google.protobuf.Timestamp
42, // 10: memos.api.v1.UserStats.memo_type_stats:type_name -> memos.api.v1.UserStats.MemoTypeStats
41, // 11: memos.api.v1.UserStats.tag_count:type_name -> memos.api.v1.UserStats.TagCountEntry
12, // 12: memos.api.v1.ListAllUserStatsResponse.stats:type_name -> memos.api.v1.UserStats
43, // 13: memos.api.v1.UserSetting.general_setting:type_name -> memos.api.v1.UserSetting.GeneralSetting
44, // 14: memos.api.v1.UserSetting.sessions_setting:type_name -> memos.api.v1.UserSetting.SessionsSetting
45, // 15: memos.api.v1.UserSetting.access_tokens_setting:type_name -> memos.api.v1.UserSetting.AccessTokensSetting
46, // 16: memos.api.v1.UserSetting.webhooks_setting:type_name -> memos.api.v1.UserSetting.WebhooksSetting
16, // 17: memos.api.v1.UpdateUserSettingRequest.setting:type_name -> memos.api.v1.UserSetting
50, // 18: memos.api.v1.UpdateUserSettingRequest.update_mask:type_name -> google.protobuf.FieldMask
16, // 19: memos.api.v1.ListUserSettingsResponse.settings:type_name -> memos.api.v1.UserSetting
49, // 20: memos.api.v1.UserAccessToken.issued_at:type_name -> google.protobuf.Timestamp
49, // 21: memos.api.v1.UserAccessToken.expires_at:type_name -> google.protobuf.Timestamp
21, // 22: memos.api.v1.ListUserAccessTokensResponse.access_tokens:type_name -> memos.api.v1.UserAccessToken
21, // 23: memos.api.v1.CreateUserAccessTokenRequest.access_token:type_name -> memos.api.v1.UserAccessToken
49, // 24: memos.api.v1.UserSession.create_time:type_name -> google.protobuf.Timestamp
49, // 25: memos.api.v1.UserSession.last_accessed_time:type_name -> google.protobuf.Timestamp
47, // 26: memos.api.v1.UserSession.client_info:type_name -> memos.api.v1.UserSession.ClientInfo
26, // 27: memos.api.v1.ListUserSessionsResponse.sessions:type_name -> memos.api.v1.UserSession
49, // 28: memos.api.v1.UserWebhook.create_time:type_name -> google.protobuf.Timestamp
49, // 29: memos.api.v1.UserWebhook.update_time:type_name -> google.protobuf.Timestamp
30, // 30: memos.api.v1.ListUserWebhooksResponse.webhooks:type_name -> memos.api.v1.UserWebhook
30, // 31: memos.api.v1.CreateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook
30, // 32: memos.api.v1.UpdateUserWebhookRequest.webhook:type_name -> memos.api.v1.UserWebhook
50, // 33: memos.api.v1.UpdateUserWebhookRequest.update_mask:type_name -> google.protobuf.FieldMask
2, // 34: memos.api.v1.UserNotification.status:type_name -> memos.api.v1.UserNotification.Status
49, // 35: memos.api.v1.UserNotification.create_time:type_name -> google.protobuf.Timestamp
3, // 36: memos.api.v1.UserNotification.type:type_name -> memos.api.v1.UserNotification.Type
36, // 37: memos.api.v1.ListUserNotificationsResponse.notifications:type_name -> memos.api.v1.UserNotification
36, // 38: memos.api.v1.UpdateUserNotificationRequest.notification:type_name -> memos.api.v1.UserNotification
50, // 39: memos.api.v1.UpdateUserNotificationRequest.update_mask:type_name -> google.protobuf.FieldMask
26, // 40: memos.api.v1.UserSetting.SessionsSetting.sessions:type_name -> memos.api.v1.UserSession
21, // 41: memos.api.v1.UserSetting.AccessTokensSetting.access_tokens:type_name -> memos.api.v1.UserAccessToken
30, // 42: memos.api.v1.UserSetting.WebhooksSetting.webhooks:type_name -> memos.api.v1.UserWebhook
5, // 43: memos.api.v1.UserService.ListUsers:input_type -> memos.api.v1.ListUsersRequest
7, // 44: memos.api.v1.UserService.GetUser:input_type -> memos.api.v1.GetUserRequest
8, // 45: memos.api.v1.UserService.CreateUser:input_type -> memos.api.v1.CreateUserRequest
9, // 46: memos.api.v1.UserService.UpdateUser:input_type -> memos.api.v1.UpdateUserRequest
10, // 47: memos.api.v1.UserService.DeleteUser:input_type -> memos.api.v1.DeleteUserRequest
11, // 48: memos.api.v1.UserService.GetUserAvatar:input_type -> memos.api.v1.GetUserAvatarRequest
14, // 49: memos.api.v1.UserService.ListAllUserStats:input_type -> memos.api.v1.ListAllUserStatsRequest
13, // 50: memos.api.v1.UserService.GetUserStats:input_type -> memos.api.v1.GetUserStatsRequest
17, // 51: memos.api.v1.UserService.GetUserSetting:input_type -> memos.api.v1.GetUserSettingRequest
18, // 52: memos.api.v1.UserService.UpdateUserSetting:input_type -> memos.api.v1.UpdateUserSettingRequest
19, // 53: memos.api.v1.UserService.ListUserSettings:input_type -> memos.api.v1.ListUserSettingsRequest
22, // 54: memos.api.v1.UserService.ListUserAccessTokens:input_type -> memos.api.v1.ListUserAccessTokensRequest
24, // 55: memos.api.v1.UserService.CreateUserAccessToken:input_type -> memos.api.v1.CreateUserAccessTokenRequest
25, // 56: memos.api.v1.UserService.DeleteUserAccessToken:input_type -> memos.api.v1.DeleteUserAccessTokenRequest
27, // 57: memos.api.v1.UserService.ListUserSessions:input_type -> memos.api.v1.ListUserSessionsRequest
29, // 58: memos.api.v1.UserService.RevokeUserSession:input_type -> memos.api.v1.RevokeUserSessionRequest
31, // 59: memos.api.v1.UserService.ListUserWebhooks:input_type -> memos.api.v1.ListUserWebhooksRequest
33, // 60: memos.api.v1.UserService.CreateUserWebhook:input_type -> memos.api.v1.CreateUserWebhookRequest
34, // 61: memos.api.v1.UserService.UpdateUserWebhook:input_type -> memos.api.v1.UpdateUserWebhookRequest
35, // 62: memos.api.v1.UserService.DeleteUserWebhook:input_type -> memos.api.v1.DeleteUserWebhookRequest
37, // 63: memos.api.v1.UserService.ListUserNotifications:input_type -> memos.api.v1.ListUserNotificationsRequest
39, // 64: memos.api.v1.UserService.UpdateUserNotification:input_type -> memos.api.v1.UpdateUserNotificationRequest
40, // 65: memos.api.v1.UserService.DeleteUserNotification:input_type -> memos.api.v1.DeleteUserNotificationRequest
6, // 66: memos.api.v1.UserService.ListUsers:output_type -> memos.api.v1.ListUsersResponse
4, // 67: memos.api.v1.UserService.GetUser:output_type -> memos.api.v1.User
4, // 68: memos.api.v1.UserService.CreateUser:output_type -> memos.api.v1.User
4, // 69: memos.api.v1.UserService.UpdateUser:output_type -> memos.api.v1.User
51, // 70: memos.api.v1.UserService.DeleteUser:output_type -> google.protobuf.Empty
52, // 71: memos.api.v1.UserService.GetUserAvatar:output_type -> google.api.HttpBody
15, // 72: memos.api.v1.UserService.ListAllUserStats:output_type -> memos.api.v1.ListAllUserStatsResponse
12, // 73: memos.api.v1.UserService.GetUserStats:output_type -> memos.api.v1.UserStats
16, // 74: memos.api.v1.UserService.GetUserSetting:output_type -> memos.api.v1.UserSetting
16, // 75: memos.api.v1.UserService.UpdateUserSetting:output_type -> memos.api.v1.UserSetting
20, // 76: memos.api.v1.UserService.ListUserSettings:output_type -> memos.api.v1.ListUserSettingsResponse
23, // 77: memos.api.v1.UserService.ListUserAccessTokens:output_type -> memos.api.v1.ListUserAccessTokensResponse
21, // 78: memos.api.v1.UserService.CreateUserAccessToken:output_type -> memos.api.v1.UserAccessToken
51, // 79: memos.api.v1.UserService.DeleteUserAccessToken:output_type -> google.protobuf.Empty
28, // 80: memos.api.v1.UserService.ListUserSessions:output_type -> memos.api.v1.ListUserSessionsResponse
51, // 81: memos.api.v1.UserService.RevokeUserSession:output_type -> google.protobuf.Empty
32, // 82: memos.api.v1.UserService.ListUserWebhooks:output_type -> memos.api.v1.ListUserWebhooksResponse
30, // 83: memos.api.v1.UserService.CreateUserWebhook:output_type -> memos.api.v1.UserWebhook
30, // 84: memos.api.v1.UserService.UpdateUserWebhook:output_type -> memos.api.v1.UserWebhook
51, // 85: memos.api.v1.UserService.DeleteUserWebhook:output_type -> google.protobuf.Empty
38, // 86: memos.api.v1.UserService.ListUserNotifications:output_type -> memos.api.v1.ListUserNotificationsResponse
36, // 87: memos.api.v1.UserService.UpdateUserNotification:output_type -> memos.api.v1.UserNotification
51, // 88: memos.api.v1.UserService.DeleteUserNotification:output_type -> google.protobuf.Empty
66, // [66:89] is the sub-list for method output_type
43, // [43:66] is the sub-list for method input_type
43, // [43:43] is the sub-list for extension type_name
43, // [43:43] is the sub-list for extension extendee
0, // [0:43] is the sub-list for field type_name
}
func init() { file_api_v1_user_service_proto_init() }
@ -2911,13 +3376,14 @@ func file_api_v1_user_service_proto_init() {
(*UserSetting_AccessTokensSetting_)(nil),
(*UserSetting_WebhooksSetting_)(nil),
}
file_api_v1_user_service_proto_msgTypes[32].OneofWrappers = []any{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v1_user_service_proto_rawDesc), len(file_api_v1_user_service_proto_rawDesc)),
NumEnums: 2,
NumMessages: 39,
NumEnums: 4,
NumMessages: 44,
NumExtensions: 0,
NumServices: 1,
},

@ -1003,6 +1003,179 @@ func local_request_UserService_DeleteUserWebhook_0(ctx context.Context, marshale
return msg, metadata, err
}
var filter_UserService_ListUserNotifications_0 = &utilities.DoubleArray{Encoding: map[string]int{"parent": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
func request_UserService_ListUserNotifications_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq ListUserNotificationsRequest
metadata runtime.ServerMetadata
err error
)
if req.Body != nil {
_, _ = 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)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListUserNotifications_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListUserNotifications(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_ListUserNotifications_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq ListUserNotificationsRequest
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)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_ListUserNotifications_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListUserNotifications(ctx, &protoReq)
return msg, metadata, err
}
var filter_UserService_UpdateUserNotification_0 = &utilities.DoubleArray{Encoding: map[string]int{"notification": 0, "name": 1}, Base: []int{1, 2, 1, 0, 0}, Check: []int{0, 1, 2, 3, 2}}
func request_UserService_UpdateUserNotification_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq UpdateUserNotificationRequest
metadata runtime.ServerMetadata
err error
)
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Notification); err != nil && !errors.Is(err, io.EOF) {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if req.Body != nil {
_, _ = io.Copy(io.Discard, req.Body)
}
if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 {
if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), protoReq.Notification); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
} else {
protoReq.UpdateMask = fieldMask
}
}
val, ok := pathParams["notification.name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "notification.name")
}
err = runtime.PopulateFieldFromPath(&protoReq, "notification.name", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "notification.name", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_UpdateUserNotification_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.UpdateUserNotification(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_UpdateUserNotification_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq UpdateUserNotificationRequest
metadata runtime.ServerMetadata
err error
)
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Notification); err != nil && !errors.Is(err, io.EOF) {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 {
if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), protoReq.Notification); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
} else {
protoReq.UpdateMask = fieldMask
}
}
val, ok := pathParams["notification.name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "notification.name")
}
err = runtime.PopulateFieldFromPath(&protoReq, "notification.name", val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "notification.name", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_UpdateUserNotification_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.UpdateUserNotification(ctx, &protoReq)
return msg, metadata, err
}
func request_UserService_DeleteUserNotification_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq DeleteUserNotificationRequest
metadata runtime.ServerMetadata
err error
)
if req.Body != nil {
_, _ = io.Copy(io.Discard, req.Body)
}
val, ok := pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := client.DeleteUserNotification(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_UserService_DeleteUserNotification_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var (
protoReq DeleteUserNotificationRequest
metadata runtime.ServerMetadata
err error
)
val, ok := pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
msg, err := server.DeleteUserNotification(ctx, &protoReq)
return msg, metadata, err
}
// RegisterUserServiceHandlerServer registers the http handlers for service UserService to "mux".
// UnaryRPC :call UserServiceServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -1409,6 +1582,66 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_DeleteUserWebhook_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodGet, pattern_UserService_ListUserNotifications_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
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.UserService/ListUserNotifications", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/notifications"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_ListUserNotifications_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_ListUserNotifications_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodPatch, pattern_UserService_UpdateUserNotification_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
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.UserService/UpdateUserNotification", runtime.WithHTTPPathPattern("/api/v1/{notification.name=users/*/notifications/*}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_UpdateUserNotification_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_UpdateUserNotification_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodDelete, pattern_UserService_DeleteUserNotification_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
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.UserService/DeleteUserNotification", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/notifications/*}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_UserService_DeleteUserNotification_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_DeleteUserNotification_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -1789,51 +2022,108 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}
forward_UserService_DeleteUserWebhook_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodGet, pattern_UserService_ListUserNotifications_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/ListUserNotifications", runtime.WithHTTPPathPattern("/api/v1/{parent=users/*}/notifications"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_ListUserNotifications_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_ListUserNotifications_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodPatch, pattern_UserService_UpdateUserNotification_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/UpdateUserNotification", runtime.WithHTTPPathPattern("/api/v1/{notification.name=users/*/notifications/*}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_UpdateUserNotification_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_UpdateUserNotification_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle(http.MethodDelete, pattern_UserService_DeleteUserNotification_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.UserService/DeleteUserNotification", runtime.WithHTTPPathPattern("/api/v1/{name=users/*/notifications/*}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_UserService_DeleteUserNotification_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_UserService_DeleteUserNotification_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, ""))
pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
pattern_UserService_GetUserAvatar_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", "name", "avatar"}, ""))
pattern_UserService_ListAllUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "stats"))
pattern_UserService_GetUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "getStats"))
pattern_UserService_GetUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "name"}, ""))
pattern_UserService_UpdateUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "setting.name"}, ""))
pattern_UserService_ListUserSettings_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", "settings"}, ""))
pattern_UserService_ListUserAccessTokens_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", "accessTokens"}, ""))
pattern_UserService_CreateUserAccessToken_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", "accessTokens"}, ""))
pattern_UserService_DeleteUserAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "accessTokens", "name"}, ""))
pattern_UserService_ListUserSessions_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", "sessions"}, ""))
pattern_UserService_RevokeUserSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "sessions", "name"}, ""))
pattern_UserService_ListUserWebhooks_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", "webhooks"}, ""))
pattern_UserService_CreateUserWebhook_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", "webhooks"}, ""))
pattern_UserService_UpdateUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "webhook.name"}, ""))
pattern_UserService_DeleteUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "name"}, ""))
pattern_UserService_ListUsers_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, ""))
pattern_UserService_DeleteUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
pattern_UserService_GetUserAvatar_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", "name", "avatar"}, ""))
pattern_UserService_ListAllUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, "stats"))
pattern_UserService_GetUserStats_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, "getStats"))
pattern_UserService_GetUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "name"}, ""))
pattern_UserService_UpdateUserSetting_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "settings", "setting.name"}, ""))
pattern_UserService_ListUserSettings_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", "settings"}, ""))
pattern_UserService_ListUserAccessTokens_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", "accessTokens"}, ""))
pattern_UserService_CreateUserAccessToken_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", "accessTokens"}, ""))
pattern_UserService_DeleteUserAccessToken_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "accessTokens", "name"}, ""))
pattern_UserService_ListUserSessions_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", "sessions"}, ""))
pattern_UserService_RevokeUserSession_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "sessions", "name"}, ""))
pattern_UserService_ListUserWebhooks_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", "webhooks"}, ""))
pattern_UserService_CreateUserWebhook_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", "webhooks"}, ""))
pattern_UserService_UpdateUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "webhook.name"}, ""))
pattern_UserService_DeleteUserWebhook_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "webhooks", "name"}, ""))
pattern_UserService_ListUserNotifications_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", "notifications"}, ""))
pattern_UserService_UpdateUserNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "notifications", "notification.name"}, ""))
pattern_UserService_DeleteUserNotification_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 2, 3, 1, 0, 4, 4, 5, 4}, []string{"api", "v1", "users", "notifications", "name"}, ""))
)
var (
forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserAvatar_0 = runtime.ForwardResponseMessage
forward_UserService_ListAllUserStats_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserStats_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserSetting_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUserSetting_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserSettings_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUserAccessToken_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserAccessToken_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserSessions_0 = runtime.ForwardResponseMessage
forward_UserService_RevokeUserSession_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserWebhooks_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUserWebhook_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUserWebhook_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserWebhook_0 = runtime.ForwardResponseMessage
forward_UserService_ListUsers_0 = runtime.ForwardResponseMessage
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUser_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserAvatar_0 = runtime.ForwardResponseMessage
forward_UserService_ListAllUserStats_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserStats_0 = runtime.ForwardResponseMessage
forward_UserService_GetUserSetting_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUserSetting_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserSettings_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserAccessTokens_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUserAccessToken_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserAccessToken_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserSessions_0 = runtime.ForwardResponseMessage
forward_UserService_RevokeUserSession_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserWebhooks_0 = runtime.ForwardResponseMessage
forward_UserService_CreateUserWebhook_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUserWebhook_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserWebhook_0 = runtime.ForwardResponseMessage
forward_UserService_ListUserNotifications_0 = runtime.ForwardResponseMessage
forward_UserService_UpdateUserNotification_0 = runtime.ForwardResponseMessage
forward_UserService_DeleteUserNotification_0 = runtime.ForwardResponseMessage
)

@ -21,26 +21,29 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers"
UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser"
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser"
UserService_GetUserAvatar_FullMethodName = "/memos.api.v1.UserService/GetUserAvatar"
UserService_ListAllUserStats_FullMethodName = "/memos.api.v1.UserService/ListAllUserStats"
UserService_GetUserStats_FullMethodName = "/memos.api.v1.UserService/GetUserStats"
UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting"
UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting"
UserService_ListUserSettings_FullMethodName = "/memos.api.v1.UserService/ListUserSettings"
UserService_ListUserAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListUserAccessTokens"
UserService_CreateUserAccessToken_FullMethodName = "/memos.api.v1.UserService/CreateUserAccessToken"
UserService_DeleteUserAccessToken_FullMethodName = "/memos.api.v1.UserService/DeleteUserAccessToken"
UserService_ListUserSessions_FullMethodName = "/memos.api.v1.UserService/ListUserSessions"
UserService_RevokeUserSession_FullMethodName = "/memos.api.v1.UserService/RevokeUserSession"
UserService_ListUserWebhooks_FullMethodName = "/memos.api.v1.UserService/ListUserWebhooks"
UserService_CreateUserWebhook_FullMethodName = "/memos.api.v1.UserService/CreateUserWebhook"
UserService_UpdateUserWebhook_FullMethodName = "/memos.api.v1.UserService/UpdateUserWebhook"
UserService_DeleteUserWebhook_FullMethodName = "/memos.api.v1.UserService/DeleteUserWebhook"
UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers"
UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser"
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser"
UserService_GetUserAvatar_FullMethodName = "/memos.api.v1.UserService/GetUserAvatar"
UserService_ListAllUserStats_FullMethodName = "/memos.api.v1.UserService/ListAllUserStats"
UserService_GetUserStats_FullMethodName = "/memos.api.v1.UserService/GetUserStats"
UserService_GetUserSetting_FullMethodName = "/memos.api.v1.UserService/GetUserSetting"
UserService_UpdateUserSetting_FullMethodName = "/memos.api.v1.UserService/UpdateUserSetting"
UserService_ListUserSettings_FullMethodName = "/memos.api.v1.UserService/ListUserSettings"
UserService_ListUserAccessTokens_FullMethodName = "/memos.api.v1.UserService/ListUserAccessTokens"
UserService_CreateUserAccessToken_FullMethodName = "/memos.api.v1.UserService/CreateUserAccessToken"
UserService_DeleteUserAccessToken_FullMethodName = "/memos.api.v1.UserService/DeleteUserAccessToken"
UserService_ListUserSessions_FullMethodName = "/memos.api.v1.UserService/ListUserSessions"
UserService_RevokeUserSession_FullMethodName = "/memos.api.v1.UserService/RevokeUserSession"
UserService_ListUserWebhooks_FullMethodName = "/memos.api.v1.UserService/ListUserWebhooks"
UserService_CreateUserWebhook_FullMethodName = "/memos.api.v1.UserService/CreateUserWebhook"
UserService_UpdateUserWebhook_FullMethodName = "/memos.api.v1.UserService/UpdateUserWebhook"
UserService_DeleteUserWebhook_FullMethodName = "/memos.api.v1.UserService/DeleteUserWebhook"
UserService_ListUserNotifications_FullMethodName = "/memos.api.v1.UserService/ListUserNotifications"
UserService_UpdateUserNotification_FullMethodName = "/memos.api.v1.UserService/UpdateUserNotification"
UserService_DeleteUserNotification_FullMethodName = "/memos.api.v1.UserService/DeleteUserNotification"
)
// UserServiceClient is the client API for UserService service.
@ -90,6 +93,12 @@ type UserServiceClient interface {
UpdateUserWebhook(ctx context.Context, in *UpdateUserWebhookRequest, opts ...grpc.CallOption) (*UserWebhook, error)
// DeleteUserWebhook deletes a webhook for a user.
DeleteUserWebhook(ctx context.Context, in *DeleteUserWebhookRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
// ListUserNotifications lists notifications for a user.
ListUserNotifications(ctx context.Context, in *ListUserNotificationsRequest, opts ...grpc.CallOption) (*ListUserNotificationsResponse, error)
// UpdateUserNotification updates a notification.
UpdateUserNotification(ctx context.Context, in *UpdateUserNotificationRequest, opts ...grpc.CallOption) (*UserNotification, error)
// DeleteUserNotification deletes a notification.
DeleteUserNotification(ctx context.Context, in *DeleteUserNotificationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
}
type userServiceClient struct {
@ -300,6 +309,36 @@ func (c *userServiceClient) DeleteUserWebhook(ctx context.Context, in *DeleteUse
return out, nil
}
func (c *userServiceClient) ListUserNotifications(ctx context.Context, in *ListUserNotificationsRequest, opts ...grpc.CallOption) (*ListUserNotificationsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListUserNotificationsResponse)
err := c.cc.Invoke(ctx, UserService_ListUserNotifications_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) UpdateUserNotification(ctx context.Context, in *UpdateUserNotificationRequest, opts ...grpc.CallOption) (*UserNotification, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(UserNotification)
err := c.cc.Invoke(ctx, UserService_UpdateUserNotification_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *userServiceClient) DeleteUserNotification(ctx context.Context, in *DeleteUserNotificationRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, UserService_DeleteUserNotification_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
// UserServiceServer is the server API for UserService service.
// All implementations must embed UnimplementedUserServiceServer
// for forward compatibility.
@ -347,6 +386,12 @@ type UserServiceServer interface {
UpdateUserWebhook(context.Context, *UpdateUserWebhookRequest) (*UserWebhook, error)
// DeleteUserWebhook deletes a webhook for a user.
DeleteUserWebhook(context.Context, *DeleteUserWebhookRequest) (*emptypb.Empty, error)
// ListUserNotifications lists notifications for a user.
ListUserNotifications(context.Context, *ListUserNotificationsRequest) (*ListUserNotificationsResponse, error)
// UpdateUserNotification updates a notification.
UpdateUserNotification(context.Context, *UpdateUserNotificationRequest) (*UserNotification, error)
// DeleteUserNotification deletes a notification.
DeleteUserNotification(context.Context, *DeleteUserNotificationRequest) (*emptypb.Empty, error)
mustEmbedUnimplementedUserServiceServer()
}
@ -417,6 +462,15 @@ func (UnimplementedUserServiceServer) UpdateUserWebhook(context.Context, *Update
func (UnimplementedUserServiceServer) DeleteUserWebhook(context.Context, *DeleteUserWebhookRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserWebhook not implemented")
}
func (UnimplementedUserServiceServer) ListUserNotifications(context.Context, *ListUserNotificationsRequest) (*ListUserNotificationsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListUserNotifications not implemented")
}
func (UnimplementedUserServiceServer) UpdateUserNotification(context.Context, *UpdateUserNotificationRequest) (*UserNotification, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateUserNotification not implemented")
}
func (UnimplementedUserServiceServer) DeleteUserNotification(context.Context, *DeleteUserNotificationRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteUserNotification not implemented")
}
func (UnimplementedUserServiceServer) mustEmbedUnimplementedUserServiceServer() {}
func (UnimplementedUserServiceServer) testEmbeddedByValue() {}
@ -798,6 +852,60 @@ func _UserService_DeleteUserWebhook_Handler(srv interface{}, ctx context.Context
return interceptor(ctx, in, info, handler)
}
func _UserService_ListUserNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListUserNotificationsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).ListUserNotifications(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_ListUserNotifications_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).ListUserNotifications(ctx, req.(*ListUserNotificationsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_UpdateUserNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateUserNotificationRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).UpdateUserNotification(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_UpdateUserNotification_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).UpdateUserNotification(ctx, req.(*UpdateUserNotificationRequest))
}
return interceptor(ctx, in, info, handler)
}
func _UserService_DeleteUserNotification_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteUserNotificationRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).DeleteUserNotification(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_DeleteUserNotification_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).DeleteUserNotification(ctx, req.(*DeleteUserNotificationRequest))
}
return interceptor(ctx, in, info, handler)
}
// UserService_ServiceDesc is the grpc.ServiceDesc for UserService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -885,6 +993,18 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
MethodName: "DeleteUserWebhook",
Handler: _UserService_DeleteUserWebhook_Handler,
},
{
MethodName: "ListUserNotifications",
Handler: _UserService_ListUserNotifications_Handler,
},
{
MethodName: "UpdateUserNotification",
Handler: _UserService_UpdateUserNotification_Handler,
},
{
MethodName: "DeleteUserNotification",
Handler: _UserService_DeleteUserNotification_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "api/v1/user_service.proto",

@ -442,71 +442,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Status'
/api/v1/inboxes/{inboxe}:
delete:
tags:
- InboxService
description: DeleteInbox deletes an inbox.
operationId: InboxService_DeleteInbox
parameters:
- name: inboxe
in: path
description: The inboxe id.
required: true
schema:
type: string
responses:
"200":
description: OK
content: {}
default:
description: Default error response
content:
application/json:
schema:
$ref: '#/components/schemas/Status'
patch:
tags:
- InboxService
description: UpdateInbox updates an inbox.
operationId: InboxService_UpdateInbox
parameters:
- name: inboxe
in: path
description: The inboxe id.
required: true
schema:
type: string
- name: updateMask
in: query
description: Required. The list of fields to update.
schema:
type: string
format: field-mask
- name: allowMissing
in: query
description: Optional. If set to true, allows updating missing fields.
schema:
type: boolean
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Inbox'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Inbox'
default:
description: Default error response
content:
application/json:
schema:
$ref: '#/components/schemas/Status'
/api/v1/memos:
get:
tags:
@ -1329,12 +1264,12 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Status'
/api/v1/users/{user}/inboxes:
/api/v1/users/{user}/notifications:
get:
tags:
- InboxService
description: ListInboxes lists inboxes for a user.
operationId: InboxService_ListInboxes
- UserService
description: ListUserNotifications lists notifications for a user.
operationId: UserService_ListUserNotifications
parameters:
- name: user
in: path
@ -1344,44 +1279,95 @@ paths:
type: string
- name: pageSize
in: query
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.
schema:
type: integer
format: int32
- name: pageToken
in: query
description: |-
Optional. A page token, received from a previous `ListInboxes` call.
Provide this to retrieve the subsequent page.
schema:
type: string
- name: filter
in: query
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
schema:
type: string
- name: orderBy
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ListUserNotificationsResponse'
default:
description: Default error response
content:
application/json:
schema:
$ref: '#/components/schemas/Status'
/api/v1/users/{user}/notifications/{notification}:
delete:
tags:
- UserService
description: DeleteUserNotification deletes a notification.
operationId: UserService_DeleteUserNotification
parameters:
- name: user
in: path
description: The user id.
required: true
schema:
type: string
- name: notification
in: path
description: The notification id.
required: true
schema:
type: string
responses:
"200":
description: OK
content: {}
default:
description: Default error response
content:
application/json:
schema:
$ref: '#/components/schemas/Status'
patch:
tags:
- UserService
description: UpdateUserNotification updates a notification.
operationId: UserService_UpdateUserNotification
parameters:
- name: user
in: path
description: The user id.
required: true
schema:
type: string
- name: notification
in: path
description: The notification id.
required: true
schema:
type: string
- name: updateMask
in: query
description: |-
Optional. The order to sort results by.
Example: "create_time desc" or "status asc"
schema:
type: string
format: field-mask
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/UserNotification'
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ListInboxesResponse'
$ref: '#/components/schemas/UserNotification'
default:
description: Default error response
content:
@ -2039,7 +2025,6 @@ components:
enum:
- TYPE_UNSPECIFIED
- MEMO_COMMENT
- VERSION_UPDATE
type: string
description: The type of the activity.
format: enum
@ -2264,52 +2249,6 @@ components:
properties:
oauth2Config:
$ref: '#/components/schemas/OAuth2Config'
Inbox:
type: object
properties:
name:
type: string
description: |-
The resource name of the inbox.
Format: inboxes/{inbox}
sender:
readOnly: true
type: string
description: |-
The sender of the inbox notification.
Format: users/{user}
receiver:
readOnly: true
type: string
description: |-
The receiver of the inbox notification.
Format: users/{user}
status:
enum:
- STATUS_UNSPECIFIED
- UNREAD
- ARCHIVED
type: string
description: The status of the inbox notification.
format: enum
createTime:
readOnly: true
type: string
description: Output only. The creation timestamp.
format: date-time
type:
readOnly: true
enum:
- TYPE_UNSPECIFIED
- MEMO_COMMENT
- VERSION_UPDATE
type: string
description: The type of the inbox notification.
format: enum
activityId:
type: integer
description: Optional. The activity ID associated with this inbox notification.
format: int32
ListActivitiesResponse:
type: object
properties:
@ -2357,23 +2296,6 @@ components:
items:
$ref: '#/components/schemas/IdentityProvider'
description: The list of identity providers.
ListInboxesResponse:
type: object
properties:
inboxes:
type: array
items:
$ref: '#/components/schemas/Inbox'
description: The list of inboxes.
nextPageToken:
type: string
description: |-
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
description: The total count of inboxes (may be approximate).
format: int32
ListMemoAttachmentsResponse:
type: object
properties:
@ -2462,6 +2384,15 @@ components:
type: integer
description: The total count of access tokens.
format: int32
ListUserNotificationsResponse:
type: object
properties:
notifications:
type: array
items:
$ref: '#/components/schemas/UserNotification'
nextPageToken:
type: string
ListUserSessionsResponse:
type: object
properties:
@ -2903,6 +2834,46 @@ components:
description: Optional. The expiration timestamp.
format: date-time
description: User access token message
UserNotification:
type: object
properties:
name:
readOnly: true
type: string
description: |-
The resource name of the notification.
Format: users/{user}/notifications/{notification}
sender:
readOnly: true
type: string
description: |-
The sender of the notification.
Format: users/{user}
status:
enum:
- STATUS_UNSPECIFIED
- UNREAD
- ARCHIVED
type: string
description: The status of the notification.
format: enum
createTime:
readOnly: true
type: string
description: The creation timestamp.
format: date-time
type:
readOnly: true
enum:
- TYPE_UNSPECIFIED
- MEMO_COMMENT
type: string
description: The type of the notification.
format: enum
activityId:
type: integer
description: The activity ID associated with this notification.
format: int32
UserSession:
type: object
properties:
@ -3223,7 +3194,6 @@ tags:
- name: AttachmentService
- name: AuthService
- name: IdentityProviderService
- name: InboxService
- name: MemoService
- name: ShortcutService
- name: UserService

@ -25,8 +25,8 @@ type InboxMessage_Type int32
const (
InboxMessage_TYPE_UNSPECIFIED InboxMessage_Type = 0
InboxMessage_MEMO_COMMENT InboxMessage_Type = 1
InboxMessage_VERSION_UPDATE InboxMessage_Type = 2
// Memo comment notification.
InboxMessage_MEMO_COMMENT InboxMessage_Type = 1
)
// Enum value maps for InboxMessage_Type.
@ -34,12 +34,10 @@ var (
InboxMessage_Type_name = map[int32]string{
0: "TYPE_UNSPECIFIED",
1: "MEMO_COMMENT",
2: "VERSION_UPDATE",
}
InboxMessage_Type_value = map[string]int32{
"TYPE_UNSPECIFIED": 0,
"MEMO_COMMENT": 1,
"VERSION_UPDATE": 2,
}
)
@ -71,9 +69,11 @@ func (InboxMessage_Type) EnumDescriptor() ([]byte, []int) {
}
type InboxMessage struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type InboxMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=memos.store.InboxMessage_Type" json:"type,omitempty"`
ActivityId *int32 `protobuf:"varint,2,opt,name=activity_id,json=activityId,proto3,oneof" json:"activity_id,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
// The type of the inbox message.
Type InboxMessage_Type `protobuf:"varint,1,opt,name=type,proto3,enum=memos.store.InboxMessage_Type" json:"type,omitempty"`
// The system-generated unique ID of related activity.
ActivityId *int32 `protobuf:"varint,2,opt,name=activity_id,json=activityId,proto3,oneof" json:"activity_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -126,15 +126,14 @@ var File_store_inbox_proto protoreflect.FileDescriptor
const file_store_inbox_proto_rawDesc = "" +
"\n" +
"\x11store/inbox.proto\x12\vmemos.store\"\xbc\x01\n" +
"\x11store/inbox.proto\x12\vmemos.store\"\xa8\x01\n" +
"\fInboxMessage\x122\n" +
"\x04type\x18\x01 \x01(\x0e2\x1e.memos.store.InboxMessage.TypeR\x04type\x12$\n" +
"\vactivity_id\x18\x02 \x01(\x05H\x00R\n" +
"activityId\x88\x01\x01\"B\n" +
"activityId\x88\x01\x01\".\n" +
"\x04Type\x12\x14\n" +
"\x10TYPE_UNSPECIFIED\x10\x00\x12\x10\n" +
"\fMEMO_COMMENT\x10\x01\x12\x12\n" +
"\x0eVERSION_UPDATE\x10\x02B\x0e\n" +
"\fMEMO_COMMENT\x10\x01B\x0e\n" +
"\f_activity_idB\x95\x01\n" +
"\x0fcom.memos.storeB\n" +
"InboxProtoP\x01Z)github.com/usememos/memos/proto/gen/store\xa2\x02\x03MSX\xaa\x02\vMemos.Store\xca\x02\vMemos\\Store\xe2\x02\x17Memos\\Store\\GPBMetadata\xea\x02\fMemos::Storeb\x06proto3"

@ -5,11 +5,14 @@ package memos.store;
option go_package = "gen/store";
message InboxMessage {
// The type of the inbox message.
Type type = 1;
// The system-generated unique ID of related activity.
optional int32 activity_id = 2;
enum Type {
TYPE_UNSPECIFIED = 0;
// Memo comment notification.
MEMO_COMMENT = 1;
VERSION_UPDATE = 2;
}
Type type = 1;
optional int32 activity_id = 2;
}

@ -64,6 +64,9 @@ func (s *APIV1Service) GetActivity(ctx context.Context, request *v1pb.GetActivit
return activityMessage, nil
}
// convertActivityFromStore converts a storage-layer activity to an API activity.
// This handles the mapping between internal activity representation and the public API,
// including proper type and level conversions.
func (s *APIV1Service) convertActivityFromStore(ctx context.Context, activity *store.Activity) (*v1pb.Activity, error) {
payload, err := s.convertActivityPayloadFromStore(ctx, activity.Payload)
if err != nil {
@ -98,9 +101,12 @@ func (s *APIV1Service) convertActivityFromStore(ctx context.Context, activity *s
}, nil
}
// convertActivityPayloadFromStore converts a storage-layer activity payload to an API payload.
// This resolves references (e.g., memo IDs) to resource names for the API.
func (s *APIV1Service) convertActivityPayloadFromStore(ctx context.Context, payload *storepb.ActivityPayload) (*v1pb.ActivityPayload, error) {
v2Payload := &v1pb.ActivityPayload{}
if payload.MemoComment != nil {
// Fetch the comment memo
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &payload.MemoComment.MemoId,
ExcludeContent: true,
@ -111,6 +117,8 @@ func (s *APIV1Service) convertActivityPayloadFromStore(ctx context.Context, payl
if memo == nil {
return nil, status.Errorf(codes.NotFound, "memo does not exist")
}
// Fetch the related memo (the one being commented on)
relatedMemo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: &payload.MemoComment.RelatedMemoId,
ExcludeContent: true,
@ -118,6 +126,7 @@ func (s *APIV1Service) convertActivityPayloadFromStore(ctx context.Context, payl
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get related memo: %v", err)
}
v2Payload.Payload = &v1pb.ActivityPayload_MemoComment{
MemoComment: &v1pb.ActivityMemoCommentPayload{
Memo: fmt.Sprintf("%s%s", MemoNamePrefix, memo.UID),

@ -1,224 +0,0 @@
package v1
import (
"context"
"fmt"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/timestamppb"
v1pb "github.com/usememos/memos/proto/gen/api/v1"
"github.com/usememos/memos/store"
)
func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxesRequest) (*v1pb.ListInboxesResponse, error) {
// Extract user ID from parent resource name
userID, err := ExtractUserIDFromName(request.Parent)
if err != nil {
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
if request.PageToken != "" {
var pageToken v1pb.PageToken
if err := unmarshalPageToken(request.PageToken, &pageToken); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid page token: %v", err)
}
limit = int(pageToken.Limit)
offset = int(pageToken.Offset)
} else {
limit = int(request.PageSize)
}
if limit <= 0 {
limit = DefaultPageSize
}
if limit > MaxPageSize {
limit = MaxPageSize
}
limitPlusOne := limit + 1
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 inboxes: %v", err)
}
inboxMessages := []*v1pb.Inbox{}
nextPageToken := ""
if len(inboxes) == limitPlusOne {
inboxes = inboxes[:limit]
nextPageToken, err = getPageToken(limit, offset+limit)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get next page token: %v", err)
}
}
for _, inbox := range inboxes {
inboxMessage := convertInboxFromStore(inbox)
if inboxMessage.Type == v1pb.Inbox_TYPE_UNSPECIFIED {
continue
}
inboxMessages = append(inboxMessages, inboxMessage)
}
response := &v1pb.ListInboxesResponse{
Inboxes: inboxMessages,
NextPageToken: nextPageToken,
TotalSize: int32(len(inboxMessages)), // For now, use actual returned count
}
return response, nil
}
func (s *APIV1Service) UpdateInbox(ctx context.Context, request *v1pb.UpdateInboxRequest) (*v1pb.Inbox, error) {
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
}
inboxID, err := ExtractInboxIDFromName(request.Inbox.Name)
if err != nil {
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 cannot be unspecified")
}
update.Status = convertInboxStatusToStore(request.Inbox.Status)
} else {
return nil, status.Errorf(codes.InvalidArgument, "unsupported field in update mask: %q", field)
}
}
inbox, err := s.Store.UpdateInbox(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update inbox: %v", err)
}
return convertInboxFromStore(inbox), nil
}
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 %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 delete inbox: %v", err)
}
return &emptypb.Empty{}, nil
}
func convertInboxFromStore(inbox *store.Inbox) *v1pb.Inbox {
return &v1pb.Inbox{
Name: fmt.Sprintf("%s%d", InboxNamePrefix, inbox.ID),
Sender: fmt.Sprintf("%s%d", UserNamePrefix, inbox.SenderID),
Receiver: fmt.Sprintf("%s%d", UserNamePrefix, inbox.ReceiverID),
Status: convertInboxStatusFromStore(inbox.Status),
CreateTime: timestamppb.New(time.Unix(inbox.CreatedTs, 0)),
Type: v1pb.Inbox_Type(inbox.Message.Type),
ActivityId: inbox.Message.ActivityId,
}
}
func convertInboxStatusFromStore(status store.InboxStatus) v1pb.Inbox_Status {
switch status {
case store.UNREAD:
return v1pb.Inbox_UNREAD
case store.ARCHIVED:
return v1pb.Inbox_ARCHIVED
default:
return v1pb.Inbox_STATUS_UNSPECIFIED
}
}
func convertInboxStatusToStore(status v1pb.Inbox_Status) store.InboxStatus {
switch status {
case v1pb.Inbox_ARCHIVED:
return store.ARCHIVED
default:
return store.UNREAD
}
}

@ -1,559 +0,0 @@
package test
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.ID)
// 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.ID)
// 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.ID)
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.ID)
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.ID)
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.ID)
// 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.ID)
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.ID)
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.ID)
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.ID)
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.ID)
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.ID)
// 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.ID)
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.ID)
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.ID)
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.ID)
// 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)
})
}

@ -1552,3 +1552,201 @@ func extractUsernameFromComparison(left, right ast.Expr) (string, bool) {
return str, true
}
// ListUserNotifications lists all notifications for a user.
// Notifications are backed by the inbox storage layer and represent activities
// that require user attention (e.g., memo comments).
func (s *APIV1Service) ListUserNotifications(ctx context.Context, request *v1pb.ListUserNotificationsRequest) (*v1pb.ListUserNotificationsResponse, error) {
userID, err := ExtractUserIDFromName(request.Parent)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
}
// Verify the requesting user has permission to view these notifications
currentUser, err := s.GetCurrentUser(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if currentUser.ID != userID {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
// Fetch inbox items from storage
inboxes, err := s.Store.ListInboxes(ctx, &store.FindInbox{
ReceiverID: &userID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to list inboxes: %v", err)
}
// Convert storage layer inboxes to API notifications
notifications := []*v1pb.UserNotification{}
for _, inbox := range inboxes {
notification, err := s.convertInboxToUserNotification(ctx, inbox)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err)
}
notifications = append(notifications, notification)
}
return &v1pb.ListUserNotificationsResponse{
Notifications: notifications,
}, nil
}
// UpdateUserNotification updates a notification's status (e.g., marking as read/archived).
// Only the notification owner can update their notifications.
func (s *APIV1Service) UpdateUserNotification(ctx context.Context, request *v1pb.UpdateUserNotificationRequest) (*v1pb.UserNotification, error) {
if request.Notification == nil {
return nil, status.Errorf(codes.InvalidArgument, "notification is required")
}
notificationID, err := ExtractNotificationIDFromName(request.Notification.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid notification name: %v", err)
}
currentUser, err := s.GetCurrentUser(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
// Verify ownership before updating
inboxes, err := s.Store.ListInboxes(ctx, &store.FindInbox{
ID: &notificationID,
})
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, "notification not found")
}
inbox := inboxes[0]
if inbox.ReceiverID != currentUser.ID {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
// Build update request based on field mask
update := &store.UpdateInbox{
ID: notificationID,
}
for _, path := range request.UpdateMask.Paths {
switch path {
case "status":
// Convert API status enum to storage enum
var inboxStatus store.InboxStatus
switch request.Notification.Status {
case v1pb.UserNotification_UNREAD:
inboxStatus = store.UNREAD
case v1pb.UserNotification_ARCHIVED:
inboxStatus = store.ARCHIVED
default:
return nil, status.Errorf(codes.InvalidArgument, "invalid status")
}
update.Status = inboxStatus
}
}
updatedInbox, err := s.Store.UpdateInbox(ctx, update)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to update inbox: %v", err)
}
notification, err := s.convertInboxToUserNotification(ctx, updatedInbox)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to convert inbox: %v", err)
}
return notification, nil
}
// DeleteUserNotification permanently deletes a notification.
// Only the notification owner can delete their notifications.
func (s *APIV1Service) DeleteUserNotification(ctx context.Context, request *v1pb.DeleteUserNotificationRequest) (*emptypb.Empty, error) {
notificationID, err := ExtractNotificationIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid notification name: %v", err)
}
currentUser, err := s.GetCurrentUser(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
// Verify ownership before deletion
inboxes, err := s.Store.ListInboxes(ctx, &store.FindInbox{
ID: &notificationID,
})
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, "notification not found")
}
inbox := inboxes[0]
if inbox.ReceiverID != currentUser.ID {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
if err := s.Store.DeleteInbox(ctx, &store.DeleteInbox{
ID: notificationID,
}); err != nil {
return nil, status.Errorf(codes.Internal, "failed to delete inbox: %v", err)
}
return &emptypb.Empty{}, nil
}
// convertInboxToUserNotification converts a storage-layer inbox to an API notification.
// This handles the mapping between the internal inbox representation and the public API.
func (s *APIV1Service) convertInboxToUserNotification(ctx context.Context, inbox *store.Inbox) (*v1pb.UserNotification, error) {
notification := &v1pb.UserNotification{
Name: fmt.Sprintf("users/%d/notifications/%d", inbox.ReceiverID, inbox.ID),
Sender: fmt.Sprintf("%s%d", UserNamePrefix, inbox.SenderID),
CreateTime: timestamppb.New(time.Unix(inbox.CreatedTs, 0)),
}
// Convert status from storage enum to API enum
switch inbox.Status {
case store.UNREAD:
notification.Status = v1pb.UserNotification_UNREAD
case store.ARCHIVED:
notification.Status = v1pb.UserNotification_ARCHIVED
default:
notification.Status = v1pb.UserNotification_STATUS_UNSPECIFIED
}
// Extract notification type and activity ID from inbox message
if inbox.Message != nil {
switch inbox.Message.Type {
case storepb.InboxMessage_MEMO_COMMENT:
notification.Type = v1pb.UserNotification_MEMO_COMMENT
default:
notification.Type = v1pb.UserNotification_TYPE_UNSPECIFIED
}
if inbox.Message.ActivityId != nil {
notification.ActivityId = inbox.Message.ActivityId
}
}
return notification, nil
}
// ExtractNotificationIDFromName extracts the notification ID from a resource name.
// Expected format: users/{user_id}/notifications/{notification_id}
func ExtractNotificationIDFromName(name string) (int32, error) {
pattern := regexp.MustCompile(`^users/(\d+)/notifications/(\d+)$`)
matches := pattern.FindStringSubmatch(name)
if len(matches) != 3 {
return 0, errors.Errorf("invalid notification name: %s", name)
}
id, err := strconv.Atoi(matches[2])
if err != nil {
return 0, errors.Errorf("invalid notification id: %s", matches[2])
}
return int32(id), nil
}

@ -29,7 +29,6 @@ type APIV1Service struct {
v1pb.UnimplementedMemoServiceServer
v1pb.UnimplementedAttachmentServiceServer
v1pb.UnimplementedShortcutServiceServer
v1pb.UnimplementedInboxServiceServer
v1pb.UnimplementedActivityServiceServer
v1pb.UnimplementedIdentityProviderServiceServer
@ -60,7 +59,6 @@ func NewAPIV1Service(secret string, profile *profile.Profile, store *store.Store
v1pb.RegisterMemoServiceServer(grpcServer, apiv1Service)
v1pb.RegisterAttachmentServiceServer(grpcServer, apiv1Service)
v1pb.RegisterShortcutServiceServer(grpcServer, apiv1Service)
v1pb.RegisterInboxServiceServer(grpcServer, apiv1Service)
v1pb.RegisterActivityServiceServer(grpcServer, apiv1Service)
v1pb.RegisterIdentityProviderServiceServer(grpcServer, apiv1Service)
reflection.Register(grpcServer)
@ -107,9 +105,6 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterShortcutServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterInboxServiceHandler(ctx, gwMux, conn); err != nil {
return err
}
if err := v1pb.RegisterActivityServiceHandler(ctx, gwMux, conn); err != nil {
return err
}

@ -6,11 +6,13 @@ import (
storepb "github.com/usememos/memos/proto/gen/store"
)
// InboxStatus is the status for an inbox.
// InboxStatus represents the status of an inbox notification.
type InboxStatus string
const (
UNREAD InboxStatus = "UNREAD"
// UNREAD indicates the notification has not been read by the user.
UNREAD InboxStatus = "UNREAD"
// ARCHIVED indicates the notification has been archived/dismissed by the user.
ARCHIVED InboxStatus = "ARCHIVED"
)
@ -18,20 +20,24 @@ func (s InboxStatus) String() string {
return string(s)
}
// Inbox represents a notification in a user's inbox.
// It connects activities to users who should be notified.
type Inbox struct {
ID int32
CreatedTs int64
SenderID int32
ReceiverID int32
Status InboxStatus
Message *storepb.InboxMessage
SenderID int32 // The user who triggered the notification
ReceiverID int32 // The user who receives the notification
Status InboxStatus // Current status (unread/archived)
Message *storepb.InboxMessage // The notification message content
}
// UpdateInbox contains fields that can be updated for an inbox item.
type UpdateInbox struct {
ID int32
Status InboxStatus
}
// FindInbox specifies filter criteria for querying inbox items.
type FindInbox struct {
ID *int32
SenderID *int32
@ -43,22 +49,27 @@ type FindInbox struct {
Offset *int
}
// DeleteInbox specifies which inbox item to delete.
type DeleteInbox struct {
ID int32
}
// CreateInbox creates a new inbox notification.
func (s *Store) CreateInbox(ctx context.Context, create *Inbox) (*Inbox, error) {
return s.driver.CreateInbox(ctx, create)
}
// ListInboxes retrieves inbox items matching the filter criteria.
func (s *Store) ListInboxes(ctx context.Context, find *FindInbox) ([]*Inbox, error) {
return s.driver.ListInboxes(ctx, find)
}
// UpdateInbox updates an existing inbox item.
func (s *Store) UpdateInbox(ctx context.Context, update *UpdateInbox) (*Inbox, error) {
return s.driver.UpdateInbox(ctx, update)
}
// DeleteInbox permanently removes an inbox item.
func (s *Store) DeleteInbox(ctx context.Context, delete *DeleteInbox) error {
return s.driver.DeleteInbox(ctx, delete)
}

@ -1,39 +1,39 @@
import { InboxIcon, LoaderIcon, MessageCircleIcon, TrashIcon } from "lucide-react";
import { CheckIcon, MessageCircleIcon, TrashIcon, XIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useState } from "react";
import toast from "react-hot-toast";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import UserAvatar from "@/components/UserAvatar";
import { activityServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect";
import useNavigateTo from "@/hooks/useNavigateTo";
import { cn } from "@/lib/utils";
import { memoStore, userStore } from "@/store";
import { activityNamePrefix } from "@/store/common";
import { Inbox, Inbox_Status } from "@/types/proto/api/v1/inbox_service";
import { Memo } from "@/types/proto/api/v1/memo_service";
import { User } from "@/types/proto/api/v1/user_service";
import { User, UserNotification, UserNotification_Status } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
interface Props {
inbox: Inbox;
notification: UserNotification;
}
const MemoCommentMessage = observer(({ inbox }: Props) => {
const MemoCommentMessage = observer(({ notification }: Props) => {
const t = useTranslate();
const navigateTo = useNavigateTo();
const [relatedMemo, setRelatedMemo] = useState<Memo | undefined>(undefined);
const [commentMemo, setCommentMemo] = useState<Memo | undefined>(undefined);
const [sender, setSender] = useState<User | undefined>(undefined);
const [initialized, setInitialized] = useState<boolean>(false);
const [hasError, setHasError] = useState<boolean>(false);
useAsyncEffect(async () => {
if (!inbox.activityId) {
if (!notification.activityId) {
return;
}
try {
const activity = await activityServiceClient.getActivity({
name: `${activityNamePrefix}${inbox.activityId}`,
name: `${activityNamePrefix}${notification.activityId}`,
});
if (activity.payload?.memoComment) {
@ -42,7 +42,14 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
skipStore: true,
});
setRelatedMemo(memo);
const sender = await userStore.getOrFetchUserByName(inbox.sender);
// Fetch the comment memo
const comment = await memoStore.getOrFetchMemoByName(memoCommentPayload.memo, {
skipStore: true,
});
setCommentMemo(comment);
const sender = await userStore.getOrFetchUserByName(notification.sender);
setSender(sender);
setInitialized(true);
}
@ -51,7 +58,7 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
setHasError(true);
return;
}
}, [inbox.activityId]);
}, [notification.activityId]);
const handleNavigateToMemo = async () => {
if (!relatedMemo) {
@ -59,16 +66,16 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
}
navigateTo(`/${relatedMemo.name}`);
if (inbox.status === Inbox_Status.UNREAD) {
if (notification.status === UserNotification_Status.UNREAD) {
handleArchiveMessage(true);
}
};
const handleArchiveMessage = async (silence = false) => {
await userStore.updateInbox(
await userStore.updateNotification(
{
name: inbox.name,
status: Inbox_Status.ARCHIVED,
name: notification.name,
status: UserNotification_Status.ARCHIVED,
},
["status"],
);
@ -78,104 +85,120 @@ const MemoCommentMessage = observer(({ inbox }: Props) => {
};
const handleDeleteMessage = async () => {
await userStore.deleteInbox(inbox.name);
await userStore.deleteNotification(notification.name);
toast.success(t("message.deleted-successfully"));
};
const deleteButton = () => (
<>
<div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<TrashIcon
className="w-4 h-auto cursor-pointer text-muted-foreground hover:text-primary"
onClick={() => handleDeleteMessage()}
/>
</TooltipTrigger>
<TooltipContent>
<p>{t("common.delete")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
if (!initialized && !hasError) {
return (
<div className="w-full px-4 py-3.5 border-b border-border last:border-b-0 bg-muted/20 animate-pulse">
<div className="flex items-start gap-3">
<div className="w-9 h-9 rounded-full bg-muted/60 shrink-0" />
<div className="flex-1 space-y-2.5">
<div className="h-3.5 bg-muted/60 rounded w-2/5" />
<div className="h-16 bg-muted/40 rounded-md" />
</div>
</div>
</div>
</>
);
);
}
const archiveButton = () => (
<>
<div>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<InboxIcon
className="w-4 h-auto cursor-pointer text-muted-foreground hover:text-primary"
onClick={() => handleArchiveMessage()}
/>
</TooltipTrigger>
<TooltipContent>
<p>{t("common.archive")}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
if (hasError) {
return (
<div className="w-full px-4 py-3.5 border-b border-border last:border-b-0 bg-destructive/[0.03]">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-full bg-destructive/10 flex items-center justify-center shrink-0">
<XIcon className="w-4 h-4 text-destructive" />
</div>
<span className="text-sm text-destructive/90">{t("inbox.failed-to-load")}</span>
</div>
<button
onClick={handleDeleteMessage}
className="p-1.5 hover:bg-destructive/10 rounded-md transition-colors"
title={t("common.delete")}
>
<TrashIcon className="w-3.5 h-3.5 text-destructive/70 hover:text-destructive" />
</button>
</div>
</div>
</>
);
);
}
const isUnread = notification.status === UserNotification_Status.UNREAD;
return (
<div className="w-full flex flex-row justify-start items-start gap-3">
<div
className={cn(
"shrink-0 mt-2 p-2 rounded-full border",
inbox.status === Inbox_Status.UNREAD
? "border-primary text-primary bg-primary/10"
: "border-muted-foreground text-muted-foreground bg-muted",
)}
>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<MessageCircleIcon className="w-4 sm:w-5 h-auto" />
</TooltipTrigger>
<TooltipContent>
<p>Comment</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div
className={cn(
"border w-full p-2 px-3 rounded-lg flex flex-col justify-start items-start gap-1 border-border hover:bg-background",
inbox.status !== Inbox_Status.UNREAD && "opacity-60",
)}
>
{initialized ? (
<>
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm text-muted-foreground">{inbox.createTime?.toLocaleString()}</span>
{inbox.status === Inbox_Status.UNREAD ? archiveButton() : deleteButton()}
<div
className={cn(
"w-full px-4 py-3.5 border-b border-border last:border-b-0 transition-colors group relative",
isUnread ? "bg-primary/[0.02] hover:bg-primary/[0.04]" : "hover:bg-muted/40",
)}
>
{/* Unread indicator bar */}
{isUnread && <div className="absolute left-0 top-0 bottom-0 w-1 bg-primary" />}
<div className="flex items-start gap-3">
{/* Avatar & Icon */}
<div className="relative shrink-0 mt-0.5">
<UserAvatar className="w-9 h-9" avatarUrl={sender?.avatarUrl} />
<div
className={cn(
"absolute -bottom-0.5 -right-0.5 w-[18px] h-[18px] rounded-full border-[2px] border-background flex items-center justify-center shadow-sm",
isUnread ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground",
)}
>
<MessageCircleIcon className="w-2.5 h-2.5" />
</div>
</div>
{/* Content */}
<div className="flex-1 min-w-0">
{/* Header */}
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0 flex items-baseline gap-1.5 flex-wrap">
<span className="font-semibold text-sm text-foreground">{sender?.displayName || sender?.username}</span>
<span className="text-sm text-muted-foreground">commented on your memo</span>
<span className="text-xs text-muted-foreground/80">
· {notification.createTime?.toLocaleDateString([], { month: "short", day: "numeric" })} at{" "}
{notification.createTime?.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}
</span>
</div>
<div className="flex items-center gap-0.5 shrink-0">
{isUnread ? (
<button
onClick={() => handleArchiveMessage()}
className="p-1.5 hover:bg-background/80 rounded-md transition-all opacity-0 group-hover:opacity-100"
title={t("common.archive")}
>
<CheckIcon className="w-3.5 h-3.5 text-muted-foreground hover:text-primary" />
</button>
) : (
<button
onClick={handleDeleteMessage}
className="p-1.5 hover:bg-background/80 rounded-md transition-all opacity-0 group-hover:opacity-100"
title={t("common.delete")}
>
<TrashIcon className="w-3.5 h-3.5 text-muted-foreground hover:text-destructive" />
</button>
)}
</div>
<p
className="text-base leading-tight cursor-pointer text-muted-foreground hover:underline hover:text-primary"
</div>
{/* Comment Preview */}
{commentMemo && (
<div
onClick={handleNavigateToMemo}
className="mt-2 p-3 rounded-md bg-muted/40 hover:bg-muted/60 cursor-pointer border border-border/50 hover:border-border transition-all group/comment"
>
{t("inbox.memo-comment", {
user: sender?.displayName || sender?.username,
memo: relatedMemo?.name,
interpolation: { escapeValue: false },
})}
</p>
</>
) : hasError ? (
<div className="w-full flex flex-row justify-between items-center">
<span className="text-sm text-muted-foreground">{t("inbox.failed-to-load")}</span>
{deleteButton()}
</div>
) : (
<div className="w-full flex flex-row justify-center items-center my-2">
<LoaderIcon className="animate-spin text-muted-foreground" />
</div>
)}
<div className="flex items-start gap-2">
<MessageCircleIcon className="w-3.5 h-3.5 text-muted-foreground/60 shrink-0 mt-0.5" />
<p className="text-[13px] text-foreground/90 line-clamp-2 leading-relaxed group-hover/comment:text-foreground transition-colors">
{commentMemo.content || <span className="italic text-muted-foreground">Empty comment</span>}
</p>
</div>
</div>
)}
</div>
</div>
</div>
);

@ -1,4 +1,4 @@
import { EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
import { BellIcon, EarthIcon, LibraryIcon, PaperclipIcon, UserCircleIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import { NavLink } from "react-router-dom";
@ -33,7 +33,7 @@ const Navigation = observer((props: Props) => {
return;
}
userStore.fetchInboxes();
userStore.fetchNotifications();
}, []);
const homeNavLink: NavLinkItem = {
@ -54,6 +54,22 @@ const Navigation = observer((props: Props) => {
title: t("common.attachments"),
icon: <PaperclipIcon className="w-6 h-auto shrink-0" />,
};
const unreadCount = userStore.state.notifications.filter((n) => n.status === "UNREAD").length;
const inboxNavLink: NavLinkItem = {
id: "header-inbox",
path: Routes.INBOX,
title: t("common.inbox"),
icon: (
<div className="relative">
<BellIcon className="w-6 h-auto shrink-0" />
{unreadCount > 0 && (
<span className="absolute -top-1 -right-1 min-w-[18px] h-[18px] px-1 flex items-center justify-center bg-primary text-primary-foreground text-[10px] font-semibold rounded-full border-2 border-background">
{unreadCount > 99 ? "99+" : unreadCount}
</span>
)}
</div>
),
};
const signInNavLink: NavLinkItem = {
id: "header-auth",
path: Routes.AUTH,
@ -61,7 +77,9 @@ const Navigation = observer((props: Props) => {
icon: <UserCircleIcon className="w-6 h-auto shrink-0" />,
};
const navLinks: NavLinkItem[] = currentUser ? [homeNavLink, exploreNavLink, attachmentsNavLink] : [exploreNavLink, signInNavLink];
const navLinks: NavLinkItem[] = currentUser
? [homeNavLink, exploreNavLink, attachmentsNavLink, inboxNavLink]
: [exploreNavLink, signInNavLink];
return (
<header className={cn("w-full h-full overflow-auto flex flex-col justify-between items-start gap-4 hide-scrollbar", className)}>

@ -1,14 +1,4 @@
import {
ArchiveIcon,
LogOutIcon,
User2Icon,
SquareUserIcon,
SettingsIcon,
BellIcon,
GlobeIcon,
PaletteIcon,
CheckIcon,
} from "lucide-react";
import { ArchiveIcon, LogOutIcon, User2Icon, SquareUserIcon, SettingsIcon, GlobeIcon, PaletteIcon, CheckIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { authServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser";
@ -99,10 +89,6 @@ const UserMenu = observer((props: Props) => {
<ArchiveIcon className="size-4 text-muted-foreground" />
{t("common.archived")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigateTo(Routes.INBOX)}>
<BellIcon className="size-4 text-muted-foreground" />
{t("common.inbox")}
</DropdownMenuItem>
<DropdownMenuSub>
<DropdownMenuSubTrigger>
<GlobeIcon className="size-4 text-muted-foreground" />

@ -3,7 +3,6 @@ import { ActivityServiceDefinition } from "./types/proto/api/v1/activity_service
import { AttachmentServiceDefinition } from "./types/proto/api/v1/attachment_service";
import { AuthServiceDefinition } from "./types/proto/api/v1/auth_service";
import { IdentityProviderServiceDefinition } from "./types/proto/api/v1/idp_service";
import { InboxServiceDefinition } from "./types/proto/api/v1/inbox_service";
import { MemoServiceDefinition } from "./types/proto/api/v1/memo_service";
import { ShortcutServiceDefinition } from "./types/proto/api/v1/shortcut_service";
import { UserServiceDefinition } from "./types/proto/api/v1/user_service";
@ -30,8 +29,6 @@ export const attachmentServiceClient = clientFactory.create(AttachmentServiceDef
export const shortcutServiceClient = clientFactory.create(ShortcutServiceDefinition, channel);
export const inboxServiceClient = clientFactory.create(InboxServiceDefinition, channel);
export const activityServiceClient = clientFactory.create(ActivityServiceDefinition, channel);
export const identityProviderServiceClient = clientFactory.create(IdentityProviderServiceDefinition, channel);

@ -18,6 +18,7 @@
"about": "About",
"add": "Add",
"admin": "Admin",
"all": "All",
"archive": "Archive",
"archived": "Archived",
"attachments": "Attachments",
@ -125,8 +126,10 @@
},
"inbox": {
"memo-comment": "{{user}} has a comment on your {{memo}}.",
"version-update": "New version {{version}} is available now!",
"failed-to-load": "Failed to load inbox item"
"failed-to-load": "Failed to load inbox item",
"unread": "Unread",
"no-unread": "No unread notifications",
"no-archived": "No archived notifications"
},
"markdown": {
"checkbox": "Checkbox",

@ -1,63 +1,126 @@
import { sortBy } from "lodash-es";
import { BellIcon } from "lucide-react";
import { ArchiveIcon, BellIcon, InboxIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import Empty from "@/components/Empty";
import MemoCommentMessage from "@/components/Inbox/MemoCommentMessage";
import MobileHeader from "@/components/MobileHeader";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import { cn } from "@/lib/utils";
import { userStore } from "@/store";
import { Inbox, Inbox_Status, Inbox_Type } from "@/types/proto/api/v1/inbox_service";
import { UserNotification, UserNotification_Status, UserNotification_Type } from "@/types/proto/api/v1/user_service";
import { useTranslate } from "@/utils/i18n";
const Inboxes = observer(() => {
const t = useTranslate();
const { md } = useResponsiveWidth();
const [filter, setFilter] = useState<"all" | "unread" | "archived">("all");
const inboxes = sortBy(userStore.state.inboxes, (inbox: Inbox) => {
if (inbox.status === Inbox_Status.UNREAD) return 0;
if (inbox.status === Inbox_Status.ARCHIVED) return 1;
return 2;
const allNotifications = sortBy(userStore.state.notifications, (notification: UserNotification) => {
return -(notification.createTime?.getTime() || 0);
});
const fetchInboxes = async () => {
const notifications = allNotifications.filter((notification) => {
if (filter === "unread") return notification.status === UserNotification_Status.UNREAD;
if (filter === "archived") return notification.status === UserNotification_Status.ARCHIVED;
return true;
});
const unreadCount = allNotifications.filter((n) => n.status === UserNotification_Status.UNREAD).length;
const archivedCount = allNotifications.filter((n) => n.status === UserNotification_Status.ARCHIVED).length;
const fetchNotifications = async () => {
try {
await userStore.fetchInboxes();
await userStore.fetchNotifications();
} catch (error) {
console.error("Failed to fetch inboxes:", error);
console.error("Failed to fetch notifications:", error);
}
};
useEffect(() => {
fetchInboxes();
fetchNotifications();
}, []);
return (
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
{!md && <MobileHeader />}
<div className="w-full px-4 sm:px-6">
<div className="w-full border border-border flex flex-col justify-start items-start px-4 py-3 rounded-xl bg-background text-foreground">
<div className="relative w-full flex flex-row justify-between items-center">
<p className="py-1 flex flex-row justify-start items-center select-none opacity-80">
<BellIcon className="w-6 h-auto mr-1 opacity-80" />
<span className="text-lg">{t("common.inbox")}</span>
</p>
<div className="w-full border border-border flex flex-col justify-start items-start rounded-xl bg-background text-foreground overflow-hidden">
{/* Header */}
<div className="w-full px-4 py-4 border-b border-border">
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row items-center gap-2">
<BellIcon className="w-5 h-auto text-muted-foreground" />
<h1 className="text-xl font-semibold">{t("common.inbox")}</h1>
{unreadCount > 0 && (
<span className="ml-1 px-2 py-0.5 text-xs font-medium rounded-full bg-primary text-primary-foreground">
{unreadCount}
</span>
)}
</div>
</div>
</div>
<div className="w-full h-auto flex flex-col justify-start items-start px-2 pb-4">
{inboxes.length === 0 && (
<div className="w-full mt-4 mb-8 flex flex-col justify-center items-center italic">
{/* Filter Tabs */}
<div className="w-full px-4 py-2 border-b border-border bg-muted/30">
<div className="flex flex-row gap-1">
<button
onClick={() => setFilter("all")}
className={cn(
"px-3 py-1.5 text-sm font-medium rounded-md transition-colors",
filter === "all"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
)}
>
{t("common.all")} ({allNotifications.length})
</button>
<button
onClick={() => setFilter("unread")}
className={cn(
"px-3 py-1.5 text-sm font-medium rounded-md transition-colors flex items-center gap-1.5",
filter === "unread"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
)}
>
<InboxIcon className="w-3.5 h-auto" />
{t("inbox.unread")} ({unreadCount})
</button>
<button
onClick={() => setFilter("archived")}
className={cn(
"px-3 py-1.5 text-sm font-medium rounded-md transition-colors flex items-center gap-1.5",
filter === "archived"
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground hover:bg-background/50",
)}
>
<ArchiveIcon className="w-3.5 h-auto" />
{t("common.archived")} ({archivedCount})
</button>
</div>
</div>
{/* Notifications List */}
<div className="w-full">
{notifications.length === 0 ? (
<div className="w-full py-16 flex flex-col justify-center items-center">
<Empty />
<p className="mt-4 text-muted-foreground">{t("message.no-data")}</p>
<p className="mt-4 text-sm text-muted-foreground">
{filter === "unread" ? t("inbox.no-unread") : filter === "archived" ? t("inbox.no-archived") : t("message.no-data")}
</p>
</div>
) : (
<div className="flex flex-col">
{notifications.map((notification: UserNotification) => {
if (notification.type === UserNotification_Type.MEMO_COMMENT) {
return <MemoCommentMessage key={notification.name} notification={notification} />;
}
return null;
})}
</div>
)}
<div className="flex flex-col justify-start items-start w-full mt-4 gap-4">
{inboxes.map((inbox: Inbox) => {
if (inbox.type === Inbox_Type.MEMO_COMMENT) {
return <MemoCommentMessage key={`${inbox.name}-${inbox.status}`} inbox={inbox} />;
}
return undefined;
})}
</div>
</div>
</div>
</div>

@ -1,10 +1,10 @@
import { uniqueId } from "lodash-es";
import { makeAutoObservable, computed } from "mobx";
import { authServiceClient, inboxServiceClient, userServiceClient, shortcutServiceClient } from "@/grpcweb";
import { Inbox } from "@/types/proto/api/v1/inbox_service";
import { authServiceClient, userServiceClient, shortcutServiceClient } from "@/grpcweb";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
import {
User,
UserNotification,
UserSetting,
UserSetting_Key,
UserSetting_GeneralSetting,
@ -24,7 +24,7 @@ class LocalState {
userAccessTokensSetting?: UserSetting_AccessTokensSetting;
userWebhooksSetting?: UserSetting_WebhooksSetting;
shortcuts: Shortcut[] = [];
inboxes: Inbox[] = [];
notifications: UserNotification[] = [];
userMapByName: Record<string, User> = {};
userStatsByName: Record<string, UserStats> = {};
@ -218,40 +218,40 @@ const userStore = (() => {
// Note: fetchShortcuts is now handled by fetchUserSettings
// The shortcuts are extracted from the user shortcuts setting
const fetchInboxes = async () => {
const fetchNotifications = async () => {
if (!state.currentUser) {
throw new Error("No current user available");
}
const { inboxes } = await inboxServiceClient.listInboxes({
const { notifications } = await userServiceClient.listUserNotifications({
parent: state.currentUser,
});
state.setPartial({
inboxes,
notifications,
});
};
const updateInbox = async (inbox: Partial<Inbox>, updateMask: string[]) => {
const updatedInbox = await inboxServiceClient.updateInbox({
inbox,
const updateNotification = async (notification: Partial<UserNotification>, updateMask: string[]) => {
const updatedNotification = await userServiceClient.updateUserNotification({
notification,
updateMask,
});
state.setPartial({
inboxes: state.inboxes.map((i) => {
if (i.name === updatedInbox.name) {
return updatedInbox;
notifications: state.notifications.map((n) => {
if (n.name === updatedNotification.name) {
return updatedNotification;
}
return i;
return n;
}),
});
return updatedInbox;
return updatedNotification;
};
const deleteInbox = async (name: string) => {
await inboxServiceClient.deleteInbox({ name });
const deleteNotification = async (name: string) => {
await userServiceClient.deleteUserNotification({ name });
state.setPartial({
inboxes: state.inboxes.filter((i) => i.name !== name),
notifications: state.notifications.filter((n) => n.name !== name),
});
};
@ -296,9 +296,9 @@ const userStore = (() => {
updateUserGeneralSetting,
getUserGeneralSetting,
fetchUserSettings,
fetchInboxes,
updateInbox,
deleteInbox,
fetchNotifications,
updateNotification,
deleteNotification,
fetchUserStats,
setStatsStateId,
};

@ -39,8 +39,6 @@ export enum Activity_Type {
TYPE_UNSPECIFIED = "TYPE_UNSPECIFIED",
/** MEMO_COMMENT - Memo comment activity. */
MEMO_COMMENT = "MEMO_COMMENT",
/** VERSION_UPDATE - Version update activity. */
VERSION_UPDATE = "VERSION_UPDATE",
UNRECOGNIZED = "UNRECOGNIZED",
}
@ -52,9 +50,6 @@ export function activity_TypeFromJSON(object: any): Activity_Type {
case 1:
case "MEMO_COMMENT":
return Activity_Type.MEMO_COMMENT;
case 2:
case "VERSION_UPDATE":
return Activity_Type.VERSION_UPDATE;
case -1:
case "UNRECOGNIZED":
default:
@ -68,8 +63,6 @@ export function activity_TypeToNumber(object: Activity_Type): number {
return 0;
case Activity_Type.MEMO_COMMENT:
return 1;
case Activity_Type.VERSION_UPDATE:
return 2;
case Activity_Type.UNRECOGNIZED:
default:
return -1;

@ -89,8 +89,6 @@ export enum Inbox_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",
}
@ -102,9 +100,6 @@ export function inbox_TypeFromJSON(object: any): Inbox_Type {
case 1:
case "MEMO_COMMENT":
return Inbox_Type.MEMO_COMMENT;
case 2:
case "VERSION_UPDATE":
return Inbox_Type.VERSION_UPDATE;
case -1:
case "UNRECOGNIZED":
default:
@ -118,8 +113,6 @@ export function inbox_TypeToNumber(object: Inbox_Type): number {
return 0;
case Inbox_Type.MEMO_COMMENT:
return 1;
case Inbox_Type.VERSION_UPDATE:
return 2;
case Inbox_Type.UNRECOGNIZED:
default:
return -1;

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save