mirror of https://github.com/usememos/memos.git
refactor: nest reaction resource names under memos
This commit is contained in:
parent
c2aea5a4b7
commit
d7284fe867
|
|
@ -100,7 +100,7 @@ service MemoService {
|
|||
}
|
||||
// DeleteMemoReaction deletes a reaction for a memo.
|
||||
rpc DeleteMemoReaction(DeleteMemoReactionRequest) returns (google.protobuf.Empty) {
|
||||
option (google.api.http) = {delete: "/api/v1/{name=reactions/*}"};
|
||||
option (google.api.http) = {delete: "/api/v1/{name=memos/*/reactions/*}"};
|
||||
option (google.api.method_signature) = "name";
|
||||
}
|
||||
}
|
||||
|
|
@ -115,14 +115,14 @@ enum Visibility {
|
|||
message Reaction {
|
||||
option (google.api.resource) = {
|
||||
type: "memos.api.v1/Reaction"
|
||||
pattern: "reactions/{reaction}"
|
||||
pattern: "memos/{memo}/reactions/{reaction}"
|
||||
name_field: "name"
|
||||
singular: "reaction"
|
||||
plural: "reactions"
|
||||
};
|
||||
|
||||
// The resource name of the reaction.
|
||||
// Format: reactions/{reaction}
|
||||
// Format: memos/{memo}/reactions/{reaction}
|
||||
string name = 1 [
|
||||
(google.api.field_behavior) = OUTPUT_ONLY,
|
||||
(google.api.field_behavior) = IDENTIFIER
|
||||
|
|
@ -501,7 +501,7 @@ message UpsertMemoReactionRequest {
|
|||
|
||||
message DeleteMemoReactionRequest {
|
||||
// Required. The resource name of the reaction to delete.
|
||||
// Format: reactions/{reaction}
|
||||
// Format: memos/{memo}/reactions/{reaction}
|
||||
string name = 1 [
|
||||
(google.api.field_behavior) = REQUIRED,
|
||||
(google.api.resource_reference) = {type: "memos.api.v1/Reaction"}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ func (MemoRelation_Type) EnumDescriptor() ([]byte, []int) {
|
|||
type Reaction struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// The resource name of the reaction.
|
||||
// Format: reactions/{reaction}
|
||||
// Format: memos/{memo}/reactions/{reaction}
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
// The resource name of the creator.
|
||||
// Format: users/{user}
|
||||
|
|
@ -1627,7 +1627,7 @@ func (x *UpsertMemoReactionRequest) GetReaction() *Reaction {
|
|||
type DeleteMemoReactionRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Required. The resource name of the reaction to delete.
|
||||
// Format: reactions/{reaction}
|
||||
// Format: memos/{memo}/reactions/{reaction}
|
||||
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
|
@ -1799,7 +1799,7 @@ var File_api_v1_memo_service_proto protoreflect.FileDescriptor
|
|||
|
||||
const file_api_v1_memo_service_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x19api/v1/memo_service.proto\x12\fmemos.api.v1\x1a\x1fapi/v1/attachment_service.proto\x1a\x13api/v1/common.proto\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\"\xce\x02\n" +
|
||||
"\x19api/v1/memo_service.proto\x12\fmemos.api.v1\x1a\x1fapi/v1/attachment_service.proto\x1a\x13api/v1/common.proto\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\"\xdb\x02\n" +
|
||||
"\bReaction\x12\x1a\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x06\xe0A\x03\xe0A\bR\x04name\x123\n" +
|
||||
"\acreator\x18\x02 \x01(\tB\x19\xe0A\x03\xfaA\x13\n" +
|
||||
|
|
@ -1809,8 +1809,8 @@ const file_api_v1_memo_service_proto_rawDesc = "" +
|
|||
"\x11memos.api.v1/MemoR\tcontentId\x12(\n" +
|
||||
"\rreaction_type\x18\x04 \x01(\tB\x03\xe0A\x02R\freactionType\x12@\n" +
|
||||
"\vcreate_time\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampB\x03\xe0A\x03R\n" +
|
||||
"createTime:K\xeaAH\n" +
|
||||
"\x15memos.api.v1/Reaction\x12\x14reactions/{reaction}\x1a\x04name*\treactions2\breaction\"\xd8\b\n" +
|
||||
"createTime:X\xeaAU\n" +
|
||||
"\x15memos.api.v1/Reaction\x12!memos/{memo}/reactions/{reaction}\x1a\x04name*\treactions2\breaction\"\xd8\b\n" +
|
||||
"\x04Memo\x12\x17\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12.\n" +
|
||||
"\x05state\x18\x02 \x01(\x0e2\x13.memos.api.v1.StateB\x03\xe0A\x02R\x05state\x123\n" +
|
||||
|
|
@ -1953,7 +1953,7 @@ const file_api_v1_memo_service_proto_rawDesc = "" +
|
|||
"\aPRIVATE\x10\x01\x12\r\n" +
|
||||
"\tPROTECTED\x10\x02\x12\n" +
|
||||
"\n" +
|
||||
"\x06PUBLIC\x10\x032\xcb\x0e\n" +
|
||||
"\x06PUBLIC\x10\x032\xd3\x0e\n" +
|
||||
"\vMemoService\x12e\n" +
|
||||
"\n" +
|
||||
"CreateMemo\x12\x1f.memos.api.v1.CreateMemoRequest\x1a\x12.memos.api.v1.Memo\"\"\xdaA\x04memo\x82\xd3\xe4\x93\x02\x15:\x04memo\"\r/api/v1/memos\x12f\n" +
|
||||
|
|
@ -1970,8 +1970,8 @@ const file_api_v1_memo_service_proto_rawDesc = "" +
|
|||
"\x11CreateMemoComment\x12&.memos.api.v1.CreateMemoCommentRequest\x1a\x12.memos.api.v1.Memo\"?\xdaA\fname,comment\x82\xd3\xe4\x93\x02*:\acomment\"\x1f/api/v1/{name=memos/*}/comments\x12\x91\x01\n" +
|
||||
"\x10ListMemoComments\x12%.memos.api.v1.ListMemoCommentsRequest\x1a&.memos.api.v1.ListMemoCommentsResponse\".\xdaA\x04name\x82\xd3\xe4\x93\x02!\x12\x1f/api/v1/{name=memos/*}/comments\x12\x95\x01\n" +
|
||||
"\x11ListMemoReactions\x12&.memos.api.v1.ListMemoReactionsRequest\x1a'.memos.api.v1.ListMemoReactionsResponse\"/\xdaA\x04name\x82\xd3\xe4\x93\x02\"\x12 /api/v1/{name=memos/*}/reactions\x12\x89\x01\n" +
|
||||
"\x12UpsertMemoReaction\x12'.memos.api.v1.UpsertMemoReactionRequest\x1a\x16.memos.api.v1.Reaction\"2\xdaA\x04name\x82\xd3\xe4\x93\x02%:\x01*\" /api/v1/{name=memos/*}/reactions\x12\x80\x01\n" +
|
||||
"\x12DeleteMemoReaction\x12'.memos.api.v1.DeleteMemoReactionRequest\x1a\x16.google.protobuf.Empty\")\xdaA\x04name\x82\xd3\xe4\x93\x02\x1c*\x1a/api/v1/{name=reactions/*}B\xa8\x01\n" +
|
||||
"\x12UpsertMemoReaction\x12'.memos.api.v1.UpsertMemoReactionRequest\x1a\x16.memos.api.v1.Reaction\"2\xdaA\x04name\x82\xd3\xe4\x93\x02%:\x01*\" /api/v1/{name=memos/*}/reactions\x12\x88\x01\n" +
|
||||
"\x12DeleteMemoReaction\x12'.memos.api.v1.DeleteMemoReactionRequest\x1a\x16.google.protobuf.Empty\"1\xdaA\x04name\x82\xd3\xe4\x93\x02$*\"/api/v1/{name=memos/*/reactions/*}B\xa8\x01\n" +
|
||||
"\x10com.memos.api.v1B\x10MemoServiceProtoP\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 (
|
||||
|
|
|
|||
|
|
@ -1001,7 +1001,7 @@ func RegisterMemoServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
|
|||
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.MemoService/DeleteMemoReaction", runtime.WithHTTPPathPattern("/api/v1/{name=reactions/*}"))
|
||||
annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.MemoService/DeleteMemoReaction", runtime.WithHTTPPathPattern("/api/v1/{name=memos/*/reactions/*}"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
|
|
@ -1280,7 +1280,7 @@ func RegisterMemoServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
|
|||
ctx, cancel := context.WithCancel(req.Context())
|
||||
defer cancel()
|
||||
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.MemoService/DeleteMemoReaction", runtime.WithHTTPPathPattern("/api/v1/{name=reactions/*}"))
|
||||
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.MemoService/DeleteMemoReaction", runtime.WithHTTPPathPattern("/api/v1/{name=memos/*/reactions/*}"))
|
||||
if err != nil {
|
||||
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||
return
|
||||
|
|
@ -1310,7 +1310,7 @@ var (
|
|||
pattern_MemoService_ListMemoComments_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "comments"}, ""))
|
||||
pattern_MemoService_ListMemoReactions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, ""))
|
||||
pattern_MemoService_UpsertMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "memos", "name", "reactions"}, ""))
|
||||
pattern_MemoService_DeleteMemoReaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "reactions", "name"}, ""))
|
||||
pattern_MemoService_DeleteMemoReaction_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", "memos", "reactions", "name"}, ""))
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -960,6 +960,35 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Status'
|
||||
/api/v1/memos/{memo}/reactions/{reaction}:
|
||||
delete:
|
||||
tags:
|
||||
- MemoService
|
||||
description: DeleteMemoReaction deletes a reaction for a memo.
|
||||
operationId: MemoService_DeleteMemoReaction
|
||||
parameters:
|
||||
- name: memo
|
||||
in: path
|
||||
description: The memo id.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: reaction
|
||||
in: path
|
||||
description: The reaction id.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content: {}
|
||||
default:
|
||||
description: Default error response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Status'
|
||||
/api/v1/memos/{memo}/relations:
|
||||
get:
|
||||
tags:
|
||||
|
|
@ -1025,29 +1054,6 @@ paths:
|
|||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Status'
|
||||
/api/v1/reactions/{reaction}:
|
||||
delete:
|
||||
tags:
|
||||
- MemoService
|
||||
description: DeleteMemoReaction deletes a reaction for a memo.
|
||||
operationId: MemoService_DeleteMemoReaction
|
||||
parameters:
|
||||
- name: reaction
|
||||
in: path
|
||||
description: The reaction id.
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
content: {}
|
||||
default:
|
||||
description: Default error response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Status'
|
||||
/api/v1/users:
|
||||
get:
|
||||
tags:
|
||||
|
|
@ -2640,7 +2646,7 @@ components:
|
|||
type: string
|
||||
description: |-
|
||||
The resource name of the reaction.
|
||||
Format: reactions/{reaction}
|
||||
Format: memos/{memo}/reactions/{reaction}
|
||||
creator:
|
||||
readOnly: true
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ func (s *APIV1Service) DeleteMemoReaction(ctx context.Context, request *v1pb.Del
|
|||
return nil, status.Errorf(codes.Unauthenticated, "user not authenticated")
|
||||
}
|
||||
|
||||
reactionID, err := ExtractReactionIDFromName(request.Name)
|
||||
_, reactionID, err := ExtractMemoReactionIDFromName(request.Name)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "invalid reaction name: %v", err)
|
||||
}
|
||||
|
|
@ -95,8 +95,10 @@ func (s *APIV1Service) DeleteMemoReaction(ctx context.Context, request *v1pb.Del
|
|||
|
||||
func convertReactionFromStore(reaction *store.Reaction) *v1pb.Reaction {
|
||||
reactionUID := fmt.Sprintf("%d", reaction.ID)
|
||||
// Generate nested resource name: memos/{memo}/reactions/{reaction}
|
||||
// reaction.ContentID already contains "memos/{memo}"
|
||||
return &v1pb.Reaction{
|
||||
Name: fmt.Sprintf("%s%s", ReactionNamePrefix, reactionUID),
|
||||
Name: fmt.Sprintf("%s/%s%s", reaction.ContentID, ReactionNamePrefix, reactionUID),
|
||||
Creator: fmt.Sprintf("%s%d", UserNamePrefix, reaction.CreatorID),
|
||||
ContentId: reaction.ContentID,
|
||||
ReactionType: reaction.ReactionType,
|
||||
|
|
|
|||
|
|
@ -105,18 +105,19 @@ func ExtractAttachmentUIDFromName(name string) (string, error) {
|
|||
return id, nil
|
||||
}
|
||||
|
||||
// ExtractReactionIDFromName returns the reaction ID from a resource name.
|
||||
// e.g., "reactions/123" -> 123.
|
||||
func ExtractReactionIDFromName(name string) (int32, error) {
|
||||
tokens, err := GetNameParentTokens(name, ReactionNamePrefix)
|
||||
// ExtractMemoReactionIDFromName returns the memo UID and reaction ID from a resource name.
|
||||
// e.g., "memos/abc/reactions/123" -> ("abc", 123).
|
||||
func ExtractMemoReactionIDFromName(name string) (string, int32, error) {
|
||||
tokens, err := GetNameParentTokens(name, MemoNamePrefix, ReactionNamePrefix)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return "", 0, err
|
||||
}
|
||||
id, err := util.ConvertStringToInt32(tokens[0])
|
||||
memoUID := tokens[0]
|
||||
reactionID, err := util.ConvertStringToInt32(tokens[1])
|
||||
if err != nil {
|
||||
return 0, errors.Errorf("invalid reaction ID %q", tokens[0])
|
||||
return "", 0, errors.Errorf("invalid reaction ID %q", tokens[1])
|
||||
}
|
||||
return id, nil
|
||||
return memoUID, reactionID, nil
|
||||
}
|
||||
|
||||
// ExtractInboxIDFromName returns the inbox ID from a resource name.
|
||||
|
|
|
|||
|
|
@ -183,8 +183,9 @@ func TestDeleteMemoReaction(t *testing.T) {
|
|||
|
||||
// Try to delete non-existent reaction - should fail with permission denied
|
||||
// (not "not found" to avoid information disclosure)
|
||||
// Use new nested resource format: memos/{memo}/reactions/{reaction}
|
||||
_, err = ts.Service.DeleteMemoReaction(userCtx, &apiv1.DeleteMemoReactionRequest{
|
||||
Name: "reactions/99999",
|
||||
Name: "memos/nonexistent/reactions/99999",
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "permission denied")
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue