-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
2,300 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
package v2 | ||
|
||
import ( | ||
"context" | ||
"strings" | ||
"time" | ||
|
||
"google.golang.org/grpc/codes" | ||
"google.golang.org/grpc/status" | ||
"google.golang.org/protobuf/types/known/timestamppb" | ||
|
||
apiv2pb "github.com/yourselfhosted/slash/proto/gen/api/v2" | ||
storepb "github.com/yourselfhosted/slash/proto/gen/store" | ||
"github.com/yourselfhosted/slash/store" | ||
) | ||
|
||
func (s *APIV2Service) ListMemos(ctx context.Context, _ *apiv2pb.ListMemosRequest) (*apiv2pb.ListMemosResponse, error) { | ||
find := &store.FindMemo{} | ||
memos, err := s.Store.ListMemos(ctx, find) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to fetch memo list, err: %v", err) | ||
} | ||
|
||
composedMemos := []*apiv2pb.Memo{} | ||
for _, memo := range memos { | ||
composedMemo, err := s.convertMemoFromStorepb(ctx, memo) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to convert memo, err: %v", err) | ||
} | ||
composedMemos = append(composedMemos, composedMemo) | ||
} | ||
|
||
response := &apiv2pb.ListMemosResponse{ | ||
Memos: composedMemos, | ||
} | ||
return response, nil | ||
} | ||
|
||
func (s *APIV2Service) GetMemo(ctx context.Context, request *apiv2pb.GetMemoRequest) (*apiv2pb.GetMemoResponse, error) { | ||
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{ | ||
ID: &request.Id, | ||
}) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to get memo by ID: %v", err) | ||
} | ||
if memo == nil { | ||
return nil, status.Errorf(codes.NotFound, "memo not found") | ||
} | ||
|
||
composedMemo, err := s.convertMemoFromStorepb(ctx, memo) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to convert memo, err: %v", err) | ||
} | ||
response := &apiv2pb.GetMemoResponse{ | ||
Memo: composedMemo, | ||
} | ||
return response, nil | ||
} | ||
|
||
func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMemoRequest) (*apiv2pb.CreateMemoResponse, error) { | ||
userID := ctx.Value(userIDContextKey).(int32) | ||
memo := &storepb.Memo{ | ||
CreatorId: userID, | ||
Name: request.Memo.Name, | ||
Title: request.Memo.Title, | ||
Content: request.Memo.Content, | ||
Tags: request.Memo.Tags, | ||
Visibility: storepb.Visibility(request.Memo.Visibility), | ||
} | ||
memo, err := s.Store.CreateMemo(ctx, memo) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to create memo, err: %v", err) | ||
} | ||
|
||
composedMemo, err := s.convertMemoFromStorepb(ctx, memo) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to convert memo, err: %v", err) | ||
} | ||
response := &apiv2pb.CreateMemoResponse{ | ||
Memo: composedMemo, | ||
} | ||
return response, nil | ||
} | ||
|
||
func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMemoRequest) (*apiv2pb.UpdateMemoResponse, error) { | ||
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 { | ||
return nil, status.Errorf(codes.InvalidArgument, "updateMask is required") | ||
} | ||
|
||
userID := ctx.Value(userIDContextKey).(int32) | ||
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{ | ||
ID: &userID, | ||
}) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to get current user, err: %v", err) | ||
} | ||
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{ | ||
ID: &request.Memo.Id, | ||
}) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to get memo by ID: %v", err) | ||
} | ||
if memo == nil { | ||
return nil, status.Errorf(codes.NotFound, "memo not found") | ||
} | ||
if memo.CreatorId != userID && currentUser.Role != store.RoleAdmin { | ||
return nil, status.Errorf(codes.PermissionDenied, "Permission denied") | ||
} | ||
|
||
update := &store.UpdateMemo{ | ||
ID: memo.Id, | ||
} | ||
for _, path := range request.UpdateMask.Paths { | ||
switch path { | ||
case "name": | ||
update.Name = &request.Memo.Name | ||
case "title": | ||
update.Title = &request.Memo.Title | ||
case "content": | ||
update.Content = &request.Memo.Content | ||
case "tags": | ||
tag := strings.Join(request.Memo.Tags, " ") | ||
update.Tag = &tag | ||
case "visibility": | ||
visibility := store.Visibility(request.Memo.Visibility.String()) | ||
update.Visibility = &visibility | ||
} | ||
} | ||
memo, err = s.Store.UpdateMemo(ctx, update) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to update memo, err: %v", err) | ||
} | ||
|
||
composedMemo, err := s.convertMemoFromStorepb(ctx, memo) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to convert memo, err: %v", err) | ||
} | ||
response := &apiv2pb.UpdateMemoResponse{ | ||
Memo: composedMemo, | ||
} | ||
return response, nil | ||
} | ||
|
||
func (s *APIV2Service) DeleteMemo(ctx context.Context, request *apiv2pb.DeleteMemoRequest) (*apiv2pb.DeleteMemoResponse, error) { | ||
userID := ctx.Value(userIDContextKey).(int32) | ||
currentUser, err := s.Store.GetUser(ctx, &store.FindUser{ | ||
ID: &userID, | ||
}) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to get current user, err: %v", err) | ||
} | ||
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{ | ||
ID: &request.Id, | ||
}) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to get memo by ID: %v", err) | ||
} | ||
if memo == nil { | ||
return nil, status.Errorf(codes.NotFound, "memo not found") | ||
} | ||
if memo.CreatorId != userID && currentUser.Role != store.RoleAdmin { | ||
return nil, status.Errorf(codes.PermissionDenied, "Permission denied") | ||
} | ||
|
||
err = s.Store.DeleteMemo(ctx, &store.DeleteMemo{ | ||
ID: memo.Id, | ||
}) | ||
if err != nil { | ||
return nil, status.Errorf(codes.Internal, "failed to delete memo, err: %v", err) | ||
} | ||
response := &apiv2pb.DeleteMemoResponse{} | ||
return response, nil | ||
} | ||
|
||
func (*APIV2Service) convertMemoFromStorepb(_ context.Context, memo *storepb.Memo) (*apiv2pb.Memo, error) { | ||
return &apiv2pb.Memo{ | ||
Id: memo.Id, | ||
CreatedTime: timestamppb.New(time.Unix(memo.CreatedTs, 0)), | ||
UpdatedTime: timestamppb.New(time.Unix(memo.UpdatedTs, 0)), | ||
CreatorId: memo.CreatorId, | ||
Name: memo.Name, | ||
Title: memo.Title, | ||
Content: memo.Content, | ||
Tags: memo.Tags, | ||
Visibility: apiv2pb.Visibility(memo.Visibility), | ||
}, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
syntax = "proto3"; | ||
|
||
package slash.api.v2; | ||
|
||
import "api/v2/common.proto"; | ||
import "google/api/annotations.proto"; | ||
import "google/api/client.proto"; | ||
import "google/protobuf/field_mask.proto"; | ||
import "google/protobuf/timestamp.proto"; | ||
|
||
option go_package = "gen/api/v2"; | ||
|
||
service MemoService { | ||
// ListMemos returns a list of memos. | ||
rpc ListMemos(ListMemosRequest) returns (ListMemosResponse) { | ||
option (google.api.http) = {get: "/api/v2/memos"}; | ||
} | ||
// GetMemo returns a memo by id. | ||
rpc GetMemo(GetMemoRequest) returns (GetMemoResponse) { | ||
option (google.api.http) = {get: "/api/v2/memos/{id}"}; | ||
option (google.api.method_signature) = "id"; | ||
} | ||
// CreateMemo creates a memo. | ||
rpc CreateMemo(CreateMemoRequest) returns (CreateMemoResponse) { | ||
option (google.api.http) = { | ||
post: "/api/v2/memos" | ||
body: "memo" | ||
}; | ||
} | ||
// UpdateMemo updates a memo. | ||
rpc UpdateMemo(UpdateMemoRequest) returns (UpdateMemoResponse) { | ||
option (google.api.http) = { | ||
put: "/api/v2/memos/{memo.id}" | ||
body: "memo" | ||
}; | ||
option (google.api.method_signature) = "memo,update_mask"; | ||
} | ||
// DeleteMemo deletes a memo by id. | ||
rpc DeleteMemo(DeleteMemoRequest) returns (DeleteMemoResponse) { | ||
option (google.api.http) = {delete: "/api/v2/memos/{id}"}; | ||
option (google.api.method_signature) = "id"; | ||
} | ||
} | ||
|
||
message Memo { | ||
int32 id = 1; | ||
|
||
int32 creator_id = 2; | ||
|
||
google.protobuf.Timestamp created_time = 3; | ||
|
||
google.protobuf.Timestamp updated_time = 4; | ||
|
||
RowStatus row_status = 5; | ||
|
||
string name = 6; | ||
|
||
string title = 7; | ||
|
||
string content = 8; | ||
|
||
repeated string tags = 9; | ||
|
||
Visibility visibility = 10; | ||
} | ||
|
||
message ListMemosRequest {} | ||
|
||
message ListMemosResponse { | ||
repeated Memo memos = 1; | ||
} | ||
|
||
message GetMemoRequest { | ||
int32 id = 1; | ||
} | ||
|
||
message GetMemoResponse { | ||
Memo memo = 1; | ||
} | ||
|
||
message CreateMemoRequest { | ||
Memo memo = 1; | ||
} | ||
|
||
message CreateMemoResponse { | ||
Memo memo = 1; | ||
} | ||
|
||
message UpdateMemoRequest { | ||
Memo memo = 1; | ||
|
||
google.protobuf.FieldMask update_mask = 2; | ||
} | ||
|
||
message UpdateMemoResponse { | ||
Memo memo = 1; | ||
} | ||
|
||
message DeleteMemoRequest { | ||
int32 id = 1; | ||
} | ||
|
||
message DeleteMemoResponse {} |
Oops, something went wrong.