Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
91c2a4ce
Commit
91c2a4ce
authored
Jun 18, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: inbox service
parent
a4920d46
Changes
10
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1175 additions
and
151 deletions
+1175
-151
inbox_service.proto
proto/api/v1/inbox_service.proto
+82
-22
inbox_service.pb.go
proto/gen/api/v1/inbox_service.pb.go
+119
-48
inbox_service.pb.gw.go
proto/gen/api/v1/inbox_service.pb.gw.go
+22
-4
apidocs.swagger.yaml
proto/gen/apidocs.swagger.yaml
+120
-43
common.go
server/router/api/v1/common.go
+2
-0
inbox_service.go
server/router/api/v1/inbox_service.go
+90
-11
inbox_service_test.go
server/router/api/v1/test/inbox_service_test.go
+559
-0
Inboxes.tsx
web/src/pages/Inboxes.tsx
+10
-1
user.ts
web/src/store/v2/user.ts
+8
-1
inbox_service.ts
web/src/types/proto/api/v1/inbox_service.ts
+163
-21
No files found.
proto/api/v1/inbox_service.proto
View file @
91c2a4ce
...
...
@@ -4,6 +4,8 @@ package memos.api.v1;
import
"google/api/annotations.proto"
;
import
"google/api/client.proto"
;
import
"google/api/field_behavior.proto"
;
import
"google/api/resource.proto"
;
import
"google/protobuf/empty.proto"
;
import
"google/protobuf/field_mask.proto"
;
import
"google/protobuf/timestamp.proto"
;
...
...
@@ -13,7 +15,8 @@ option go_package = "gen/api/v1";
service
InboxService
{
// ListInboxes lists inboxes for a user.
rpc
ListInboxes
(
ListInboxesRequest
)
returns
(
ListInboxesResponse
)
{
option
(
google.api.http
)
=
{
get
:
"/api/v1/inboxes"
};
option
(
google.api.http
)
=
{
get
:
"/api/v1/{parent=users/*}/inboxes"
};
option
(
google.api.method_signature
)
=
"parent"
;
}
// UpdateInbox updates an inbox.
rpc
UpdateInbox
(
UpdateInboxRequest
)
returns
(
Inbox
)
{
...
...
@@ -31,59 +34,116 @@ service InboxService {
}
message
Inbox
{
// The name of the inbox.
// Format: inboxes/{id}, id is the system generated auto-incremented id.
string
name
=
1
;
option
(
google.api.resource
)
=
{
type
:
"memos.api.v1/Inbox"
pattern
:
"inboxes/{inbox}"
name_field
:
"name"
singular
:
"inbox"
plural
:
"inboxes"
};
// The resource name of the inbox.
// Format: inboxes/{inbox}
string
name
=
1
[(
google.api.field_behavior
)
=
IDENTIFIER
];
// The sender of the inbox notification.
// Format: users/{user}
string
sender
=
2
;
string
sender
=
2
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The receiver of the inbox notification.
// Format: users/{user}
string
receiver
=
3
;
string
receiver
=
3
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The status of the inbox notification.
Status
status
=
4
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Output only. The creation timestamp.
google.protobuf.Timestamp
create_time
=
5
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// The type of the inbox notification.
Type
type
=
6
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// Optional. The activity ID associated with this inbox notification.
optional
int32
activity_id
=
7
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Status enumeration for inbox notifications.
enum
Status
{
// Unspecified status.
STATUS_UNSPECIFIED
=
0
;
// The notification is unread.
UNREAD
=
1
;
// The notification is archived.
ARCHIVED
=
2
;
}
Status
status
=
4
;
google.protobuf.Timestamp
create_time
=
5
;
// Type enumeration for inbox notifications.
enum
Type
{
// Unspecified type.
TYPE_UNSPECIFIED
=
0
;
// Memo comment notification.
MEMO_COMMENT
=
1
;
// Version update notification.
VERSION_UPDATE
=
2
;
}
Type
type
=
6
;
optional
int32
activity_id
=
7
;
}
message
ListInboxesRequest
{
// Required. The parent resource whose inboxes will be listed.
// Format: users/{user}
string
user
=
1
;
string
parent
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/User"
}
];
// Optional. The maximum number of inboxes to return.
// The service may return fewer than this value.
// If unspecified, at most 50 inboxes will be returned.
// The maximum value is 1000; values above 1000 will be coerced to 1000.
int32
page_size
=
2
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. A page token, received from a previous `ListInboxes` call.
// Provide this to retrieve the subsequent page.
string
page_token
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
// The maximum number of inbox to return.
int32
page_size
=
2
;
// Optional. Filter to apply to the list results.
// Example: "status=UNREAD" or "type=MEMO_COMMENT"
// Supported operators: =, !=
// Supported fields: status, type, sender, create_time
string
filter
=
4
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Provide this to retrieve the subsequent page.
string
page_token
=
3
;
// Optional. The order to sort results by.
// Example: "create_time desc" or "status asc"
string
order_by
=
5
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
ListInboxesResponse
{
// The list of inboxes.
repeated
Inbox
inboxes
=
1
;
// A token
, which
can be sent as `page_token` to retrieve the next page.
// A token
that
can be sent as `page_token` to retrieve the next page.
// If this field is omitted, there are no subsequent pages.
string
next_page_token
=
2
;
// The total count of inboxes (may be approximate).
int32
total_size
=
3
;
}
message
UpdateInboxRequest
{
Inbox
inbox
=
1
;
// Required. The inbox to update.
Inbox
inbox
=
1
[(
google.api.field_behavior
)
=
REQUIRED
];
// Required. The list of fields to update.
google.protobuf.FieldMask
update_mask
=
2
[(
google.api.field_behavior
)
=
REQUIRED
];
google.protobuf.FieldMask
update_mask
=
2
;
// Optional. If set to true, allows updating missing fields.
bool
allow_missing
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
DeleteInboxRequest
{
// The name of the inbox to delete.
string
name
=
1
;
// Required. The resource name of the inbox to delete.
// Format: inboxes/{inbox}
string
name
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/Inbox"
}
];
}
proto/gen/api/v1/inbox_service.pb.go
View file @
91c2a4ce
This diff is collapsed.
Click to expand it.
proto/gen/api/v1/inbox_service.pb.gw.go
View file @
91c2a4ce
...
...
@@ -35,14 +35,23 @@ var (
_
=
metadata
.
Join
)
var
filter_InboxService_ListInboxes_0
=
&
utilities
.
DoubleArray
{
Encoding
:
map
[
string
]
int
{
},
Base
:
[]
int
(
nil
),
Check
:
[]
int
(
nil
)
}
var
filter_InboxService_ListInboxes_0
=
&
utilities
.
DoubleArray
{
Encoding
:
map
[
string
]
int
{
"parent"
:
0
},
Base
:
[]
int
{
1
,
1
,
0
},
Check
:
[]
int
{
0
,
1
,
2
}
}
func
request_InboxService_ListInboxes_0
(
ctx
context
.
Context
,
marshaler
runtime
.
Marshaler
,
client
InboxServiceClient
,
req
*
http
.
Request
,
pathParams
map
[
string
]
string
)
(
proto
.
Message
,
runtime
.
ServerMetadata
,
error
)
{
var
(
protoReq
ListInboxesRequest
metadata
runtime
.
ServerMetadata
err
error
)
io
.
Copy
(
io
.
Discard
,
req
.
Body
)
val
,
ok
:=
pathParams
[
"parent"
]
if
!
ok
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"missing parameter %s"
,
"parent"
)
}
protoReq
.
Parent
,
err
=
runtime
.
String
(
val
)
if
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"type mismatch, parameter: %s, error: %v"
,
"parent"
,
err
)
}
if
err
:=
req
.
ParseForm
();
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"%v"
,
err
)
}
...
...
@@ -57,7 +66,16 @@ func local_request_InboxService_ListInboxes_0(ctx context.Context, marshaler run
var
(
protoReq
ListInboxesRequest
metadata
runtime
.
ServerMetadata
err
error
)
val
,
ok
:=
pathParams
[
"parent"
]
if
!
ok
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"missing parameter %s"
,
"parent"
)
}
protoReq
.
Parent
,
err
=
runtime
.
String
(
val
)
if
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"type mismatch, parameter: %s, error: %v"
,
"parent"
,
err
)
}
if
err
:=
req
.
ParseForm
();
err
!=
nil
{
return
nil
,
metadata
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"%v"
,
err
)
}
...
...
@@ -195,7 +213,7 @@ func RegisterInboxServiceHandlerServer(ctx context.Context, mux *runtime.ServeMu
var
stream
runtime
.
ServerTransportStream
ctx
=
grpc
.
NewContextWithServerTransportStream
(
ctx
,
&
stream
)
inboundMarshaler
,
outboundMarshaler
:=
runtime
.
MarshalerForRequest
(
mux
,
req
)
annotatedContext
,
err
:=
runtime
.
AnnotateIncomingContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/inboxes"
))
annotatedContext
,
err
:=
runtime
.
AnnotateIncomingContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/
{parent=users/*}/
inboxes"
))
if
err
!=
nil
{
runtime
.
HTTPError
(
ctx
,
mux
,
outboundMarshaler
,
w
,
req
,
err
)
return
...
...
@@ -293,7 +311,7 @@ func RegisterInboxServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
ctx
,
cancel
:=
context
.
WithCancel
(
req
.
Context
())
defer
cancel
()
inboundMarshaler
,
outboundMarshaler
:=
runtime
.
MarshalerForRequest
(
mux
,
req
)
annotatedContext
,
err
:=
runtime
.
AnnotateContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/inboxes"
))
annotatedContext
,
err
:=
runtime
.
AnnotateContext
(
ctx
,
mux
,
req
,
"/memos.api.v1.InboxService/ListInboxes"
,
runtime
.
WithHTTPPathPattern
(
"/api/v1/
{parent=users/*}/
inboxes"
))
if
err
!=
nil
{
runtime
.
HTTPError
(
ctx
,
mux
,
outboundMarshaler
,
w
,
req
,
err
)
return
...
...
@@ -344,7 +362,7 @@ func RegisterInboxServiceHandlerClient(ctx context.Context, mux *runtime.ServeMu
}
var
(
pattern_InboxService_ListInboxes_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
},
[]
string
{
"api"
,
"v1
"
,
"inboxes"
},
""
))
pattern_InboxService_ListInboxes_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
,
1
,
0
,
4
,
2
,
5
,
3
,
2
,
4
},
[]
string
{
"api"
,
"v1"
,
"users"
,
"parent
"
,
"inboxes"
},
""
))
pattern_InboxService_UpdateInbox_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
,
1
,
0
,
4
,
2
,
5
,
3
},
[]
string
{
"api"
,
"v1"
,
"inboxes"
,
"inbox.name"
},
""
))
pattern_InboxService_DeleteInbox_0
=
runtime
.
MustPattern
(
runtime
.
NewPattern
(
1
,
[]
int
{
2
,
0
,
2
,
1
,
2
,
2
,
1
,
0
,
4
,
2
,
5
,
3
},
[]
string
{
"api"
,
"v1"
,
"inboxes"
,
"name"
},
""
))
)
...
...
proto/gen/apidocs.swagger.yaml
View file @
91c2a4ce
...
...
@@ -292,38 +292,6 @@ paths:
type: string
tags
:
-
IdentityProviderService
/api/v1/inboxes
:
get
:
summary
:
ListInboxes lists inboxes for a user.
operationId
:
InboxService_ListInboxes
responses
:
"
200"
:
description
:
A successful response.
schema
:
$ref
:
'
#/definitions/v1ListInboxesResponse'
default
:
description
:
An unexpected error response.
schema
:
$ref
:
'
#/definitions/googlerpcStatus'
parameters
:
-
name
:
user
description
:
'
Format:
users/{user}'
in
:
query
required
:
false
type
:
string
-
name
:
pageSize
description
:
The maximum number of inbox to return.
in
:
query
required
:
false
type
:
integer
format
:
int32
-
name
:
pageToken
description
:
Provide this to retrieve the subsequent page.
in
:
query
required
:
false
type
:
string
tags
:
-
InboxService
/api/v1/markdown/link:metadata
:
get
:
summary
:
GetLinkMetadata returns metadata for a given link.
...
...
@@ -937,13 +905,14 @@ paths:
parameters
:
-
name
:
inbox.name
description
:
|-
The name of the inbox.
Format: inboxes/{i
d}, id is the system generated auto-incremented id.
The
resource
name of the inbox.
Format: inboxes/{i
nbox}
in: path
required: true
type: string
pattern: inboxes/[^/]+
-
name
:
inbox
description
:
Required. The inbox to update.
in
:
body
required
:
true
schema
:
...
...
@@ -951,20 +920,40 @@ paths:
properties
:
sender
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The sender of the inbox notification.
Format: users/{user}
readOnly: true
receiver
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The receiver of the inbox notification.
Format: users/{user}
readOnly: true
status
:
$ref
:
'
#/definitions/v1InboxStatus'
description
:
The status of the inbox notification.
createTime
:
type
:
string
format
:
date-time
description
:
Output only. The creation timestamp.
readOnly
:
true
type
:
$ref
:
'
#/definitions/v1InboxType'
description
:
The type of the inbox notification.
readOnly
:
true
activityId
:
type
:
integer
format
:
int32
description
:
Optional. The activity ID associated with this inbox notification.
title
:
Required. The inbox to update.
required
:
-
inbox
-
name
:
allowMissing
description
:
Optional. If set to
true
, allows updating missing fields.
in
:
query
required
:
false
type
:
boolean
tags
:
-
InboxService
/api/v1/{memo.name}
:
...
...
@@ -1263,7 +1252,9 @@ paths:
$ref
:
'
#/definitions/googlerpcStatus'
parameters
:
-
name
:
name_4
description
:
The name of the inbox to delete.
description
:
|-
Required. The resource name of the inbox to delete.
Format: inboxes/{inbox}
in: path
required: true
type: string
...
...
@@ -1810,6 +1801,63 @@ paths:
type
:
string
tags
:
-
UserService
/api/v1/{parent}/inboxes
:
get
:
summary
:
ListInboxes lists inboxes for a user.
operationId
:
InboxService_ListInboxes
responses
:
"
200"
:
description
:
A successful response.
schema
:
$ref
:
'
#/definitions/v1ListInboxesResponse'
default
:
description
:
An unexpected error response.
schema
:
$ref
:
'
#/definitions/googlerpcStatus'
parameters
:
-
name
:
parent
description
:
|-
Required. The parent resource whose inboxes will be listed.
Format: users/{user}
in: path
required: true
type: string
pattern: users/[^/]+
-
name
:
pageSize
description
:
|-
Optional. The maximum number of inboxes to return.
The service may return fewer than this value.
If unspecified, at most 50 inboxes will be returned.
The maximum value is 1000; values above 1000 will be coerced to 1000.
in: query
required: false
type: integer
format: int32
-
name
:
pageToken
description
:
|-
Optional. A page token, received from a previous `ListInboxes` call.
Provide this to retrieve the subsequent page.
in: query
required: false
type: string
-
name
:
filter
description
:
|-
Optional. Filter to apply to the list results.
Example: "status=UNREAD" or "type=MEMO_COMMENT"
Supported operators: =, !=
Supported fields: status, type, sender, create_time
in: query
required: false
type: string
-
name
:
orderBy
description
:
|-
Optional. The order to sort results by.
Example: "create_time desc" or "status asc"
in: query
required: false
type: string
tags
:
-
InboxService
/api/v1/{parent}/memos
:
get
:
summary
:
ListMemos lists memos with pagination and filter.
...
...
@@ -3170,25 +3218,37 @@ definitions:
properties
:
name
:
type
:
string
description
:
|-
The name of the inbox.
Format: inboxes/{i
d}, id is the system generated auto-incremented id.
title
:
|-
The
resource
name of the inbox.
Format: inboxes/{i
nbox}
sender
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The sender of the inbox notification.
Format: users/{user}
readOnly: true
receiver
:
type
:
string
title
:
'
Format:
users/{user}'
title
:
|-
The receiver of the inbox notification.
Format: users/{user}
readOnly: true
status
:
$ref
:
'
#/definitions/v1InboxStatus'
description
:
The status of the inbox notification.
createTime
:
type
:
string
format
:
date-time
description
:
Output only. The creation timestamp.
readOnly
:
true
type
:
$ref
:
'
#/definitions/v1InboxType'
description
:
The type of the inbox notification.
readOnly
:
true
activityId
:
type
:
integer
format
:
int32
description
:
Optional. The activity ID associated with this inbox notification.
v1InboxStatus
:
type
:
string
enum
:
...
...
@@ -3196,6 +3256,12 @@ definitions:
-
UNREAD
-
ARCHIVED
default
:
STATUS_UNSPECIFIED
description
:
|-
Status enumeration for inbox notifications.
- STATUS_UNSPECIFIED: Unspecified status.
- UNREAD: The notification is unread.
- ARCHIVED: The notification is archived.
v1InboxType
:
type
:
string
enum
:
...
...
@@ -3203,6 +3269,12 @@ definitions:
-
MEMO_COMMENT
-
VERSION_UPDATE
default
:
TYPE_UNSPECIFIED
description
:
|-
Type enumeration for inbox notifications.
- TYPE_UNSPECIFIED: Unspecified type.
- MEMO_COMMENT: Memo comment notification.
- VERSION_UPDATE: Version update notification.
v1ItalicNode
:
type
:
object
properties
:
...
...
@@ -3303,11 +3375,16 @@ definitions:
items
:
type
:
object
$ref
:
'
#/definitions/v1Inbox'
description
:
The list of inboxes.
nextPageToken
:
type
:
string
description
:
|-
A token
, which
can be sent as `page_token` to retrieve the next page.
A token
that
can be sent as `page_token` to retrieve the next page.
If this field is omitted, there are no subsequent pages.
totalSize
:
type
:
integer
format
:
int32
description
:
The total count of inboxes (may be approximate).
v1ListMemoAttachmentsResponse
:
type
:
object
properties
:
...
...
server/router/api/v1/common.go
View file @
91c2a4ce
...
...
@@ -13,6 +13,8 @@ import (
const
(
// DefaultPageSize is the default page size for requests.
DefaultPageSize
=
10
// MaxPageSize is the maximum page size for requests.
MaxPageSize
=
1000
)
func
convertStateFromStore
(
rowStatus
store
.
RowStatus
)
v1pb
.
State
{
...
...
server/router/api/v1/inbox_service.go
View file @
91c2a4ce
...
...
@@ -15,9 +15,27 @@ import (
)
func
(
s
*
APIV1Service
)
ListInboxes
(
ctx
context
.
Context
,
request
*
v1pb
.
ListInboxesRequest
)
(
*
v1pb
.
ListInboxesResponse
,
error
)
{
user
,
err
:=
s
.
GetCurrentUser
(
ctx
)
// Extract user ID from parent resource name
userID
,
err
:=
ExtractUserIDFromName
(
request
.
Parent
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get user"
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid parent name %q: %v"
,
request
.
Parent
,
err
)
}
// Get current user for authorization
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user"
)
}
if
currentUser
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"user not authenticated"
)
}
// Check if current user can access the requested user's inboxes
if
currentUser
.
ID
!=
userID
{
// Only allow hosts and admins to access other users' inboxes
if
currentUser
.
Role
!=
store
.
RoleHost
&&
currentUser
.
Role
!=
store
.
RoleAdmin
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"cannot access inboxes for user %q"
,
request
.
Parent
)
}
}
var
limit
,
offset
int
...
...
@@ -34,15 +52,20 @@ func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxe
if
limit
<=
0
{
limit
=
DefaultPageSize
}
if
limit
>
MaxPageSize
{
limit
=
MaxPageSize
}
limitPlusOne
:=
limit
+
1
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ReceiverID
:
&
user
.
ID
,
findInbox
:=
&
store
.
FindInbox
{
ReceiverID
:
&
userID
,
Limit
:
&
limitPlusOne
,
Offset
:
&
offset
,
})
}
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
findInbox
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list inbox: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list inbox
es
: %v"
,
err
)
}
inboxMessages
:=
[]
*
v1pb
.
Inbox
{}
...
...
@@ -51,7 +74,7 @@ func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxe
inboxes
=
inboxes
[
:
limit
]
nextPageToken
,
err
=
getPageToken
(
limit
,
offset
+
limit
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get next page token
, error
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get next page token: %v"
,
err
)
}
}
for
_
,
inbox
:=
range
inboxes
{
...
...
@@ -65,6 +88,7 @@ func (s *APIV1Service) ListInboxes(ctx context.Context, request *v1pb.ListInboxe
response
:=
&
v1pb
.
ListInboxesResponse
{
Inboxes
:
inboxMessages
,
NextPageToken
:
nextPageToken
,
TotalSize
:
int32
(
len
(
inboxMessages
)),
// For now, use actual returned count
}
return
response
,
nil
}
...
...
@@ -76,17 +100,46 @@ func (s *APIV1Service) UpdateInbox(ctx context.Context, request *v1pb.UpdateInbo
inboxID
,
err
:=
ExtractInboxIDFromName
(
request
.
Inbox
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name %q: %v"
,
request
.
Inbox
.
Name
,
err
)
}
// Get current user for authorization
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user"
)
}
if
currentUser
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"user not authenticated"
)
}
// Get the existing inbox to verify ownership
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ID
:
&
inboxID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get inbox: %v"
,
err
)
}
if
len
(
inboxes
)
==
0
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"inbox %q not found"
,
request
.
Inbox
.
Name
)
}
existingInbox
:=
inboxes
[
0
]
// Check if current user can update this inbox (must be the receiver)
if
currentUser
.
ID
!=
existingInbox
.
ReceiverID
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"cannot update inbox for another user"
)
}
update
:=
&
store
.
UpdateInbox
{
ID
:
inboxID
,
}
for
_
,
field
:=
range
request
.
UpdateMask
.
Paths
{
if
field
==
"status"
{
if
request
.
Inbox
.
Status
==
v1pb
.
Inbox_STATUS_UNSPECIFIED
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"status
is requir
ed"
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"status
cannot be unspecifi
ed"
)
}
update
.
Status
=
convertInboxStatusToStore
(
request
.
Inbox
.
Status
)
}
else
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"unsupported field in update mask: %q"
,
field
)
}
}
...
...
@@ -101,13 +154,39 @@ func (s *APIV1Service) UpdateInbox(ctx context.Context, request *v1pb.UpdateInbo
func
(
s
*
APIV1Service
)
DeleteInbox
(
ctx
context
.
Context
,
request
*
v1pb
.
DeleteInboxRequest
)
(
*
emptypb
.
Empty
,
error
)
{
inboxID
,
err
:=
ExtractInboxIDFromName
(
request
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid inbox name %q: %v"
,
request
.
Name
,
err
)
}
// Get current user for authorization
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get current user"
)
}
if
currentUser
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Unauthenticated
,
"user not authenticated"
)
}
// Get the existing inbox to verify ownership
inboxes
,
err
:=
s
.
Store
.
ListInboxes
(
ctx
,
&
store
.
FindInbox
{
ID
:
&
inboxID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get inbox: %v"
,
err
)
}
if
len
(
inboxes
)
==
0
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"inbox %q not found"
,
request
.
Name
)
}
existingInbox
:=
inboxes
[
0
]
// Check if current user can delete this inbox (must be the receiver)
if
currentUser
.
ID
!=
existingInbox
.
ReceiverID
{
return
nil
,
status
.
Errorf
(
codes
.
PermissionDenied
,
"cannot delete inbox for another user"
)
}
if
err
:=
s
.
Store
.
DeleteInbox
(
ctx
,
&
store
.
DeleteInbox
{
ID
:
inboxID
,
});
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to
upda
te inbox: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to
dele
te inbox: %v"
,
err
)
}
return
&
emptypb
.
Empty
{},
nil
}
...
...
server/router/api/v1/test/inbox_service_test.go
0 → 100644
View file @
91c2a4ce
This diff is collapsed.
Click to expand it.
web/src/pages/Inboxes.tsx
View file @
91c2a4ce
...
...
@@ -13,14 +13,23 @@ import { useTranslate } from "@/utils/i18n";
const
Inboxes
=
observer
(()
=>
{
const
t
=
useTranslate
();
const
{
md
}
=
useResponsiveWidth
();
const
inboxes
=
sortBy
(
userStore
.
state
.
inboxes
,
(
inbox
)
=>
{
if
(
inbox
.
status
===
Inbox_Status
.
UNREAD
)
return
0
;
if
(
inbox
.
status
===
Inbox_Status
.
ARCHIVED
)
return
1
;
return
2
;
});
const
fetchInboxes
=
async
()
=>
{
try
{
await
userStore
.
fetchInboxes
();
}
catch
(
error
)
{
console
.
error
(
"Failed to fetch inboxes:"
,
error
);
}
};
useEffect
(()
=>
{
userStore
.
fetchInboxes
();
fetchInboxes
();
},
[]);
return
(
...
...
web/src/store/v2/user.ts
View file @
91c2a4ce
...
...
@@ -160,7 +160,14 @@ const userStore = (() => {
};
const
fetchInboxes
=
async
()
=>
{
const
{
inboxes
}
=
await
inboxServiceClient
.
listInboxes
({});
if
(
!
state
.
currentUser
)
{
throw
new
Error
(
"No current user available"
);
}
const
{
inboxes
}
=
await
inboxServiceClient
.
listInboxes
({
parent
:
state
.
currentUser
,
});
state
.
setPartial
({
inboxes
,
});
...
...
web/src/types/proto/api/v1/inbox_service.ts
View file @
91c2a4ce
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment