init commit
This commit is contained in:
226
server/router/api/v1/inbox_service.go
Normal file
226
server/router/api/v1/inbox_service.go
Normal file
@@ -0,0 +1,226 @@
|
||||
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_UNREAD:
|
||||
return store.UNREAD
|
||||
case v1pb.Inbox_ARCHIVED:
|
||||
return store.ARCHIVED
|
||||
default:
|
||||
return store.UNREAD
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user