Commit 8d8e9b3b authored by Steven's avatar Steven

refactor: shortcut service

parent 1ea4cc45
...@@ -4,6 +4,8 @@ package memos.api.v1; ...@@ -4,6 +4,8 @@ package memos.api.v1;
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/api/client.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/empty.proto";
import "google/protobuf/field_mask.proto"; import "google/protobuf/field_mask.proto";
...@@ -16,6 +18,12 @@ service ShortcutService { ...@@ -16,6 +18,12 @@ service ShortcutService {
option (google.api.method_signature) = "parent"; option (google.api.method_signature) = "parent";
} }
// GetShortcut gets a shortcut by name.
rpc GetShortcut(GetShortcutRequest) returns (Shortcut) {
option (google.api.http) = {get: "/api/v1/{name=users/*/shortcuts/*}"};
option (google.api.method_signature) = "name";
}
// CreateShortcut creates a new shortcut for a user. // CreateShortcut creates a new shortcut for a user.
rpc CreateShortcut(CreateShortcutRequest) returns (Shortcut) { rpc CreateShortcut(CreateShortcutRequest) returns (Shortcut) {
option (google.api.http) = { option (google.api.http) = {
...@@ -28,56 +36,101 @@ service ShortcutService { ...@@ -28,56 +36,101 @@ service ShortcutService {
// UpdateShortcut updates a shortcut for a user. // UpdateShortcut updates a shortcut for a user.
rpc UpdateShortcut(UpdateShortcutRequest) returns (Shortcut) { rpc UpdateShortcut(UpdateShortcutRequest) returns (Shortcut) {
option (google.api.http) = { option (google.api.http) = {
patch: "/api/v1/{parent=users/*}/shortcuts/{shortcut.id}" patch: "/api/v1/{shortcut.name=users/*/shortcuts/*}"
body: "shortcut" body: "shortcut"
}; };
option (google.api.method_signature) = "parent,shortcut,update_mask"; option (google.api.method_signature) = "shortcut,update_mask";
} }
// DeleteShortcut deletes a shortcut for a user. // DeleteShortcut deletes a shortcut for a user.
rpc DeleteShortcut(DeleteShortcutRequest) returns (google.protobuf.Empty) { rpc DeleteShortcut(DeleteShortcutRequest) returns (google.protobuf.Empty) {
option (google.api.http) = {delete: "/api/v1/{parent=users/*}/shortcuts/{id}"}; option (google.api.http) = {delete: "/api/v1/{name=users/*/shortcuts/*}"};
option (google.api.method_signature) = "parent,id"; option (google.api.method_signature) = "name";
} }
} }
message Shortcut { message Shortcut {
string id = 1; option (google.api.resource) = {
string title = 2; type: "memos.api.v1/Shortcut"
string filter = 3; pattern: "users/{user}/shortcuts/{shortcut}"
singular: "shortcut"
plural: "shortcuts"
};
// The resource name of the shortcut.
// Format: users/{user}/shortcuts/{shortcut}
string name = 1 [(google.api.field_behavior) = IDENTIFIER];
// The title of the shortcut.
string title = 2 [(google.api.field_behavior) = REQUIRED];
// The filter expression for the shortcut.
string filter = 3 [(google.api.field_behavior) = OPTIONAL];
} }
message ListShortcutsRequest { message ListShortcutsRequest {
// The name of the user. // Required. The parent resource where shortcuts are listed.
string parent = 1; // Format: users/{user}
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {child_type: "memos.api.v1/Shortcut"}
];
// Optional. The maximum number of shortcuts to return.
int32 page_size = 2 [(google.api.field_behavior) = OPTIONAL];
// Optional. A page token for pagination.
string page_token = 3 [(google.api.field_behavior) = OPTIONAL];
} }
message ListShortcutsResponse { message ListShortcutsResponse {
// The list of shortcuts.
repeated Shortcut shortcuts = 1; repeated Shortcut shortcuts = 1;
}
message CreateShortcutRequest { // A token for the next page of results.
// The name of the user. string next_page_token = 2;
string parent = 1;
Shortcut shortcut = 2; // The total count of shortcuts.
int32 total_size = 3;
}
bool validate_only = 3; message GetShortcutRequest {
// Required. The resource name of the shortcut to retrieve.
// Format: users/{user}/shortcuts/{shortcut}
string name = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {type: "memos.api.v1/Shortcut"}
];
} }
message UpdateShortcutRequest { message CreateShortcutRequest {
// The name of the user. // Required. The parent resource where this shortcut will be created.
string parent = 1; // Format: users/{user}
string parent = 1 [
(google.api.field_behavior) = REQUIRED,
(google.api.resource_reference) = {child_type: "memos.api.v1/Shortcut"}
];
// Required. The shortcut to create.
Shortcut shortcut = 2 [(google.api.field_behavior) = REQUIRED];
// Optional. If set, validate the request, but do not actually create the shortcut.
bool validate_only = 3 [(google.api.field_behavior) = OPTIONAL];
}
Shortcut shortcut = 2; message UpdateShortcutRequest {
// Required. The shortcut resource which replaces the resource on the server.
Shortcut shortcut = 1 [(google.api.field_behavior) = REQUIRED];
google.protobuf.FieldMask update_mask = 3; // Optional. The list of fields to update.
google.protobuf.FieldMask update_mask = 2 [(google.api.field_behavior) = OPTIONAL];
} }
message DeleteShortcutRequest { message DeleteShortcutRequest {
// The name of the user. // Required. The resource name of the shortcut to delete.
string parent = 1; // Format: users/{user}/shortcuts/{shortcut}
string name = 1 [
// The id of the shortcut. (google.api.field_behavior) = REQUIRED,
string id = 2; (google.api.resource_reference) = {type: "memos.api.v1/Shortcut"}
];
} }
This diff is collapsed.
This diff is collapsed.
...@@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9 ...@@ -21,6 +21,7 @@ const _ = grpc.SupportPackageIsVersion9
const ( const (
ShortcutService_ListShortcuts_FullMethodName = "/memos.api.v1.ShortcutService/ListShortcuts" ShortcutService_ListShortcuts_FullMethodName = "/memos.api.v1.ShortcutService/ListShortcuts"
ShortcutService_GetShortcut_FullMethodName = "/memos.api.v1.ShortcutService/GetShortcut"
ShortcutService_CreateShortcut_FullMethodName = "/memos.api.v1.ShortcutService/CreateShortcut" ShortcutService_CreateShortcut_FullMethodName = "/memos.api.v1.ShortcutService/CreateShortcut"
ShortcutService_UpdateShortcut_FullMethodName = "/memos.api.v1.ShortcutService/UpdateShortcut" ShortcutService_UpdateShortcut_FullMethodName = "/memos.api.v1.ShortcutService/UpdateShortcut"
ShortcutService_DeleteShortcut_FullMethodName = "/memos.api.v1.ShortcutService/DeleteShortcut" ShortcutService_DeleteShortcut_FullMethodName = "/memos.api.v1.ShortcutService/DeleteShortcut"
...@@ -32,6 +33,8 @@ const ( ...@@ -32,6 +33,8 @@ const (
type ShortcutServiceClient interface { type ShortcutServiceClient interface {
// ListShortcuts returns a list of shortcuts for a user. // ListShortcuts returns a list of shortcuts for a user.
ListShortcuts(ctx context.Context, in *ListShortcutsRequest, opts ...grpc.CallOption) (*ListShortcutsResponse, error) ListShortcuts(ctx context.Context, in *ListShortcutsRequest, opts ...grpc.CallOption) (*ListShortcutsResponse, error)
// GetShortcut gets a shortcut by name.
GetShortcut(ctx context.Context, in *GetShortcutRequest, opts ...grpc.CallOption) (*Shortcut, error)
// CreateShortcut creates a new shortcut for a user. // CreateShortcut creates a new shortcut for a user.
CreateShortcut(ctx context.Context, in *CreateShortcutRequest, opts ...grpc.CallOption) (*Shortcut, error) CreateShortcut(ctx context.Context, in *CreateShortcutRequest, opts ...grpc.CallOption) (*Shortcut, error)
// UpdateShortcut updates a shortcut for a user. // UpdateShortcut updates a shortcut for a user.
...@@ -58,6 +61,16 @@ func (c *shortcutServiceClient) ListShortcuts(ctx context.Context, in *ListShort ...@@ -58,6 +61,16 @@ func (c *shortcutServiceClient) ListShortcuts(ctx context.Context, in *ListShort
return out, nil return out, nil
} }
func (c *shortcutServiceClient) GetShortcut(ctx context.Context, in *GetShortcutRequest, opts ...grpc.CallOption) (*Shortcut, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Shortcut)
err := c.cc.Invoke(ctx, ShortcutService_GetShortcut_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *shortcutServiceClient) CreateShortcut(ctx context.Context, in *CreateShortcutRequest, opts ...grpc.CallOption) (*Shortcut, error) { func (c *shortcutServiceClient) CreateShortcut(ctx context.Context, in *CreateShortcutRequest, opts ...grpc.CallOption) (*Shortcut, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Shortcut) out := new(Shortcut)
...@@ -94,6 +107,8 @@ func (c *shortcutServiceClient) DeleteShortcut(ctx context.Context, in *DeleteSh ...@@ -94,6 +107,8 @@ func (c *shortcutServiceClient) DeleteShortcut(ctx context.Context, in *DeleteSh
type ShortcutServiceServer interface { type ShortcutServiceServer interface {
// ListShortcuts returns a list of shortcuts for a user. // ListShortcuts returns a list of shortcuts for a user.
ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error) ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error)
// GetShortcut gets a shortcut by name.
GetShortcut(context.Context, *GetShortcutRequest) (*Shortcut, error)
// CreateShortcut creates a new shortcut for a user. // CreateShortcut creates a new shortcut for a user.
CreateShortcut(context.Context, *CreateShortcutRequest) (*Shortcut, error) CreateShortcut(context.Context, *CreateShortcutRequest) (*Shortcut, error)
// UpdateShortcut updates a shortcut for a user. // UpdateShortcut updates a shortcut for a user.
...@@ -113,6 +128,9 @@ type UnimplementedShortcutServiceServer struct{} ...@@ -113,6 +128,9 @@ type UnimplementedShortcutServiceServer struct{}
func (UnimplementedShortcutServiceServer) ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error) { func (UnimplementedShortcutServiceServer) ListShortcuts(context.Context, *ListShortcutsRequest) (*ListShortcutsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListShortcuts not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListShortcuts not implemented")
} }
func (UnimplementedShortcutServiceServer) GetShortcut(context.Context, *GetShortcutRequest) (*Shortcut, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetShortcut not implemented")
}
func (UnimplementedShortcutServiceServer) CreateShortcut(context.Context, *CreateShortcutRequest) (*Shortcut, error) { func (UnimplementedShortcutServiceServer) CreateShortcut(context.Context, *CreateShortcutRequest) (*Shortcut, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateShortcut not implemented") return nil, status.Errorf(codes.Unimplemented, "method CreateShortcut not implemented")
} }
...@@ -161,6 +179,24 @@ func _ShortcutService_ListShortcuts_Handler(srv interface{}, ctx context.Context ...@@ -161,6 +179,24 @@ func _ShortcutService_ListShortcuts_Handler(srv interface{}, ctx context.Context
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _ShortcutService_GetShortcut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetShortcutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ShortcutServiceServer).GetShortcut(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ShortcutService_GetShortcut_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ShortcutServiceServer).GetShortcut(ctx, req.(*GetShortcutRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ShortcutService_CreateShortcut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _ShortcutService_CreateShortcut_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateShortcutRequest) in := new(CreateShortcutRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
...@@ -226,6 +262,10 @@ var ShortcutService_ServiceDesc = grpc.ServiceDesc{ ...@@ -226,6 +262,10 @@ var ShortcutService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListShortcuts", MethodName: "ListShortcuts",
Handler: _ShortcutService_ListShortcuts_Handler, Handler: _ShortcutService_ListShortcuts_Handler,
}, },
{
MethodName: "GetShortcut",
Handler: _ShortcutService_GetShortcut_Handler,
},
{ {
MethodName: "CreateShortcut", MethodName: "CreateShortcut",
Handler: _ShortcutService_CreateShortcut_Handler, Handler: _ShortcutService_CreateShortcut_Handler,
......
...@@ -1083,6 +1083,52 @@ paths: ...@@ -1083,6 +1083,52 @@ paths:
tags: tags:
- ResourceService - ResourceService
/api/v1/{name_5}: /api/v1/{name_5}:
get:
summary: GetShortcut gets a shortcut by name.
operationId: ShortcutService_GetShortcut
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1Shortcut'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name_5
description: |-
Required. The resource name of the shortcut to retrieve.
Format: users/{user}/shortcuts/{shortcut}
in: path
required: true
type: string
pattern: users/[^/]+/shortcuts/[^/]+
tags:
- ShortcutService
delete:
summary: DeleteMemo deletes a memo.
operationId: MemoService_DeleteMemo
responses:
"200":
description: A successful response.
schema:
type: object
properties: {}
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name_5
description: The name of the memo.
in: path
required: true
type: string
pattern: memos/[^/]+
tags:
- MemoService
/api/v1/{name_6}:
get: get:
summary: GetWebhook gets a webhook by name. summary: GetWebhook gets a webhook by name.
operationId: WebhookService_GetWebhook operationId: WebhookService_GetWebhook
...@@ -1096,7 +1142,7 @@ paths: ...@@ -1096,7 +1142,7 @@ paths:
schema: schema:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
parameters: parameters:
- name: name_5 - name: name_6
description: |- description: |-
Required. The resource name of the webhook. Required. The resource name of the webhook.
Format: webhooks/{webhook} Format: webhooks/{webhook}
...@@ -1114,8 +1160,8 @@ paths: ...@@ -1114,8 +1160,8 @@ paths:
tags: tags:
- WebhookService - WebhookService
delete: delete:
summary: DeleteMemo deletes a memo. summary: DeleteShortcut deletes a shortcut for a user.
operationId: MemoService_DeleteMemo operationId: ShortcutService_DeleteShortcut
responses: responses:
"200": "200":
description: A successful response. description: A successful response.
...@@ -1127,15 +1173,17 @@ paths: ...@@ -1127,15 +1173,17 @@ paths:
schema: schema:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
parameters: parameters:
- name: name_5 - name: name_6
description: The name of the memo. description: |-
Required. The resource name of the shortcut to delete.
Format: users/{user}/shortcuts/{shortcut}
in: path in: path
required: true required: true
type: string type: string
pattern: memos/[^/]+ pattern: users/[^/]+/shortcuts/[^/]+
tags: tags:
- MemoService - ShortcutService
/api/v1/{name_6}: /api/v1/{name_7}:
get: get:
summary: Gets a workspace setting. summary: Gets a workspace setting.
operationId: WorkspaceService_GetWorkspaceSetting operationId: WorkspaceService_GetWorkspaceSetting
...@@ -1149,7 +1197,7 @@ paths: ...@@ -1149,7 +1197,7 @@ paths:
schema: schema:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
parameters: parameters:
- name: name_6 - name: name_7
description: |- description: |-
The resource name of the workspace setting. The resource name of the workspace setting.
Format: workspace/settings/{setting} Format: workspace/settings/{setting}
...@@ -1173,7 +1221,7 @@ paths: ...@@ -1173,7 +1221,7 @@ paths:
schema: schema:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
parameters: parameters:
- name: name_6 - name: name_7
description: |- description: |-
Required. The resource name of the webhook to delete. Required. The resource name of the webhook to delete.
Format: webhooks/{webhook} Format: webhooks/{webhook}
...@@ -1676,11 +1724,24 @@ paths: ...@@ -1676,11 +1724,24 @@ paths:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
parameters: parameters:
- name: parent - name: parent
description: The name of the user. description: |-
Required. The parent resource where shortcuts are listed.
Format: users/{user}
in: path in: path
required: true required: true
type: string type: string
pattern: users/[^/]+ pattern: users/[^/]+
- name: pageSize
description: Optional. The maximum number of shortcuts to return.
in: query
required: false
type: integer
format: int32
- name: pageToken
description: Optional. A page token for pagination.
in: query
required: false
type: string
tags: tags:
- ShortcutService - ShortcutService
post: post:
...@@ -1697,86 +1758,28 @@ paths: ...@@ -1697,86 +1758,28 @@ paths:
$ref: '#/definitions/googlerpcStatus' $ref: '#/definitions/googlerpcStatus'
parameters: parameters:
- name: parent - name: parent
description: The name of the user. description: |-
Required. The parent resource where this shortcut will be created.
Format: users/{user}
in: path in: path
required: true required: true
type: string type: string
pattern: users/[^/]+ pattern: users/[^/]+
- name: shortcut - name: shortcut
description: Required. The shortcut to create.
in: body in: body
required: true required: true
schema: schema:
$ref: '#/definitions/apiv1Shortcut' $ref: '#/definitions/apiv1Shortcut'
required:
- shortcut
- name: validateOnly - name: validateOnly
description: Optional. If set, validate the request, but do not actually create the shortcut.
in: query in: query
required: false required: false
type: boolean type: boolean
tags: tags:
- ShortcutService - ShortcutService
/api/v1/{parent}/shortcuts/{id}:
delete:
summary: DeleteShortcut deletes a shortcut for a user.
operationId: ShortcutService_DeleteShortcut
responses:
"200":
description: A successful response.
schema:
type: object
properties: {}
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: parent
description: The name of the user.
in: path
required: true
type: string
pattern: users/[^/]+
- name: id
description: The id of the shortcut.
in: path
required: true
type: string
tags:
- ShortcutService
/api/v1/{parent}/shortcuts/{shortcut.id}:
patch:
summary: UpdateShortcut updates a shortcut for a user.
operationId: ShortcutService_UpdateShortcut
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1Shortcut'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: parent
description: The name of the user.
in: path
required: true
type: string
pattern: users/[^/]+
- name: shortcut.id
in: path
required: true
type: string
- name: shortcut
in: body
required: true
schema:
type: object
properties:
title:
type: string
filter:
type: string
tags:
- ShortcutService
/api/v1/{parent}/tags/{tag}: /api/v1/{parent}/tags/{tag}:
delete: delete:
summary: DeleteMemoTag deletes a tag for a memo. summary: DeleteMemoTag deletes a tag for a memo.
...@@ -1972,6 +1975,47 @@ paths: ...@@ -1972,6 +1975,47 @@ paths:
- setting - setting
tags: tags:
- UserService - UserService
/api/v1/{shortcut.name}:
patch:
summary: UpdateShortcut updates a shortcut for a user.
operationId: ShortcutService_UpdateShortcut
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiv1Shortcut'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: shortcut.name
description: |-
The resource name of the shortcut.
Format: users/{user}/shortcuts/{shortcut}
in: path
required: true
type: string
pattern: users/[^/]+/shortcuts/[^/]+
- name: shortcut
description: Required. The shortcut resource which replaces the resource on the server.
in: body
required: true
schema:
type: object
properties:
title:
type: string
description: The title of the shortcut.
filter:
type: string
description: The filter expression for the shortcut.
title: Required. The shortcut resource which replaces the resource on the server.
required:
- title
- shortcut
tags:
- ShortcutService
/api/v1/{user.name}: /api/v1/{user.name}:
patch: patch:
summary: UpdateUser updates a user. summary: UpdateUser updates a user.
...@@ -2479,12 +2523,19 @@ definitions: ...@@ -2479,12 +2523,19 @@ definitions:
apiv1Shortcut: apiv1Shortcut:
type: object type: object
properties: properties:
id: name:
type: string type: string
title: |-
The resource name of the shortcut.
Format: users/{user}/shortcuts/{shortcut}
title: title:
type: string type: string
description: The title of the shortcut.
filter: filter:
type: string type: string
description: The filter expression for the shortcut.
required:
- title
apiv1UserSetting: apiv1UserSetting:
type: object type: object
properties: properties:
...@@ -3072,6 +3123,14 @@ definitions: ...@@ -3072,6 +3123,14 @@ definitions:
items: items:
type: object type: object
$ref: '#/definitions/apiv1Shortcut' $ref: '#/definitions/apiv1Shortcut'
description: The list of shortcuts.
nextPageToken:
type: string
description: A token for the next page of results.
totalSize:
type: integer
format: int32
description: The total count of shortcuts.
v1ListUserAccessTokensResponse: v1ListUserAccessTokensResponse:
type: object type: object
properties: properties:
......
...@@ -2,6 +2,8 @@ package v1 ...@@ -2,6 +2,8 @@ package v1
import ( import (
"context" "context"
"fmt"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
...@@ -15,6 +17,32 @@ import ( ...@@ -15,6 +17,32 @@ import (
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
// Helper function to extract user ID and shortcut ID from shortcut resource name.
// Format: users/{user}/shortcuts/{shortcut}.
func extractUserAndShortcutIDFromName(name string) (int32, string, error) {
parts := strings.Split(name, "/")
if len(parts) != 4 || parts[0] != "users" || parts[2] != "shortcuts" {
return 0, "", errors.Errorf("invalid shortcut name format: %s", name)
}
userID, err := util.ConvertStringToInt32(parts[1])
if err != nil {
return 0, "", errors.Errorf("invalid user ID %q", parts[1])
}
shortcutID := parts[3]
if shortcutID == "" {
return 0, "", errors.Errorf("empty shortcut ID in name: %s", name)
}
return userID, shortcutID, nil
}
// Helper function to construct shortcut resource name.
func constructShortcutName(userID int32, shortcutID string) string {
return fmt.Sprintf("users/%d/shortcuts/%s", userID, shortcutID)
}
func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShortcutsRequest) (*v1pb.ListShortcutsResponse, error) { func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShortcutsRequest) (*v1pb.ListShortcutsResponse, error) {
userID, err := ExtractUserIDFromName(request.Parent) userID, err := ExtractUserIDFromName(request.Parent)
if err != nil { if err != nil {
...@@ -46,7 +74,7 @@ func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShor ...@@ -46,7 +74,7 @@ func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShor
shortcuts := []*v1pb.Shortcut{} shortcuts := []*v1pb.Shortcut{}
for _, shortcut := range shortcutsUserSetting.GetShortcuts() { for _, shortcut := range shortcutsUserSetting.GetShortcuts() {
shortcuts = append(shortcuts, &v1pb.Shortcut{ shortcuts = append(shortcuts, &v1pb.Shortcut{
Id: shortcut.GetId(), Name: constructShortcutName(userID, shortcut.GetId()),
Title: shortcut.GetTitle(), Title: shortcut.GetTitle(),
Filter: shortcut.GetFilter(), Filter: shortcut.GetFilter(),
}) })
...@@ -57,6 +85,45 @@ func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShor ...@@ -57,6 +85,45 @@ func (s *APIV1Service) ListShortcuts(ctx context.Context, request *v1pb.ListShor
}, nil }, nil
} }
func (s *APIV1Service) GetShortcut(ctx context.Context, request *v1pb.GetShortcutRequest) (*v1pb.Shortcut, error) {
userID, shortcutID, err := extractUserAndShortcutIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid shortcut name: %v", err)
}
currentUser, err := s.GetCurrentUser(ctx)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if currentUser == nil || currentUser.ID != userID {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
userSetting, err := s.Store.GetUserSetting(ctx, &store.FindUserSetting{
UserID: &userID,
Key: storepb.UserSettingKey_SHORTCUTS,
})
if err != nil {
return nil, err
}
if userSetting == nil {
return nil, status.Errorf(codes.NotFound, "shortcut not found")
}
shortcutsUserSetting := userSetting.GetShortcuts()
for _, shortcut := range shortcutsUserSetting.GetShortcuts() {
if shortcut.GetId() == shortcutID {
return &v1pb.Shortcut{
Name: constructShortcutName(userID, shortcut.GetId()),
Title: shortcut.GetTitle(),
Filter: shortcut.GetFilter(),
}, nil
}
}
return nil, status.Errorf(codes.NotFound, "shortcut not found")
}
func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateShortcutRequest) (*v1pb.Shortcut, error) { func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateShortcutRequest) (*v1pb.Shortcut, error) {
userID, err := ExtractUserIDFromName(request.Parent) userID, err := ExtractUserIDFromName(request.Parent)
if err != nil { if err != nil {
...@@ -84,7 +151,7 @@ func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateS ...@@ -84,7 +151,7 @@ func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateS
} }
if request.ValidateOnly { if request.ValidateOnly {
return &v1pb.Shortcut{ return &v1pb.Shortcut{
Id: newShortcut.GetId(), Name: constructShortcutName(userID, newShortcut.GetId()),
Title: newShortcut.GetTitle(), Title: newShortcut.GetTitle(),
Filter: newShortcut.GetFilter(), Filter: newShortcut.GetFilter(),
}, nil }, nil
...@@ -123,16 +190,16 @@ func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateS ...@@ -123,16 +190,16 @@ func (s *APIV1Service) CreateShortcut(ctx context.Context, request *v1pb.CreateS
} }
return &v1pb.Shortcut{ return &v1pb.Shortcut{
Id: request.Shortcut.GetId(), Name: constructShortcutName(userID, newShortcut.GetId()),
Title: request.Shortcut.GetTitle(), Title: newShortcut.GetTitle(),
Filter: request.Shortcut.GetFilter(), Filter: newShortcut.GetFilter(),
}, nil }, nil
} }
func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateShortcutRequest) (*v1pb.Shortcut, error) { func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateShortcutRequest) (*v1pb.Shortcut, error) {
userID, err := ExtractUserIDFromName(request.Parent) userID, shortcutID, err := extractUserAndShortcutIDFromName(request.Shortcut.Name)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) return nil, status.Errorf(codes.InvalidArgument, "invalid shortcut name: %v", err)
} }
currentUser, err := s.GetCurrentUser(ctx) currentUser, err := s.GetCurrentUser(ctx)
...@@ -159,9 +226,11 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS ...@@ -159,9 +226,11 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS
shortcutsUserSetting := userSetting.GetShortcuts() shortcutsUserSetting := userSetting.GetShortcuts()
shortcuts := shortcutsUserSetting.GetShortcuts() shortcuts := shortcutsUserSetting.GetShortcuts()
var foundShortcut *storepb.ShortcutsUserSetting_Shortcut
newShortcuts := make([]*storepb.ShortcutsUserSetting_Shortcut, 0, len(shortcuts)) newShortcuts := make([]*storepb.ShortcutsUserSetting_Shortcut, 0, len(shortcuts))
for _, shortcut := range shortcuts { for _, shortcut := range shortcuts {
if shortcut.GetId() == request.Shortcut.GetId() { if shortcut.GetId() == shortcutID {
foundShortcut = shortcut
for _, field := range request.UpdateMask.Paths { for _, field := range request.UpdateMask.Paths {
if field == "title" { if field == "title" {
if request.Shortcut.GetTitle() == "" { if request.Shortcut.GetTitle() == "" {
...@@ -178,6 +247,11 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS ...@@ -178,6 +247,11 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS
} }
newShortcuts = append(newShortcuts, shortcut) newShortcuts = append(newShortcuts, shortcut)
} }
if foundShortcut == nil {
return nil, status.Errorf(codes.NotFound, "shortcut not found")
}
shortcutsUserSetting.Shortcuts = newShortcuts shortcutsUserSetting.Shortcuts = newShortcuts
userSetting.Value = &storepb.UserSetting_Shortcuts{ userSetting.Value = &storepb.UserSetting_Shortcuts{
Shortcuts: shortcutsUserSetting, Shortcuts: shortcutsUserSetting,
...@@ -188,16 +262,16 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS ...@@ -188,16 +262,16 @@ func (s *APIV1Service) UpdateShortcut(ctx context.Context, request *v1pb.UpdateS
} }
return &v1pb.Shortcut{ return &v1pb.Shortcut{
Id: request.Shortcut.GetId(), Name: constructShortcutName(userID, foundShortcut.GetId()),
Title: request.Shortcut.GetTitle(), Title: foundShortcut.GetTitle(),
Filter: request.Shortcut.GetFilter(), Filter: foundShortcut.GetFilter(),
}, nil }, nil
} }
func (s *APIV1Service) DeleteShortcut(ctx context.Context, request *v1pb.DeleteShortcutRequest) (*emptypb.Empty, error) { func (s *APIV1Service) DeleteShortcut(ctx context.Context, request *v1pb.DeleteShortcutRequest) (*emptypb.Empty, error) {
userID, err := ExtractUserIDFromName(request.Parent) userID, shortcutID, err := extractUserAndShortcutIDFromName(request.Name)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) return nil, status.Errorf(codes.InvalidArgument, "invalid shortcut name: %v", err)
} }
currentUser, err := s.GetCurrentUser(ctx) currentUser, err := s.GetCurrentUser(ctx)
...@@ -223,7 +297,7 @@ func (s *APIV1Service) DeleteShortcut(ctx context.Context, request *v1pb.DeleteS ...@@ -223,7 +297,7 @@ func (s *APIV1Service) DeleteShortcut(ctx context.Context, request *v1pb.DeleteS
shortcuts := shortcutsUserSetting.GetShortcuts() shortcuts := shortcutsUserSetting.GetShortcuts()
newShortcuts := make([]*storepb.ShortcutsUserSetting_Shortcut, 0, len(shortcuts)) newShortcuts := make([]*storepb.ShortcutsUserSetting_Shortcut, 0, len(shortcuts))
for _, shortcut := range shortcuts { for _, shortcut := range shortcuts {
if shortcut.GetId() != request.Id { if shortcut.GetId() != shortcutID {
newShortcuts = append(newShortcuts, shortcut) newShortcuts = append(newShortcuts, shortcut)
} }
} }
......
...@@ -8,7 +8,6 @@ import useLoading from "@/hooks/useLoading"; ...@@ -8,7 +8,6 @@ import useLoading from "@/hooks/useLoading";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service"; import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
import { generateUUID } from "@/utils/uuid";
import { generateDialog } from "./Dialog"; import { generateDialog } from "./Dialog";
interface Props extends DialogProps { interface Props extends DialogProps {
...@@ -20,7 +19,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => { ...@@ -20,7 +19,7 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
const t = useTranslate(); const t = useTranslate();
const user = useCurrentUser(); const user = useCurrentUser();
const [shortcut, setShortcut] = useState<Shortcut>({ const [shortcut, setShortcut] = useState<Shortcut>({
id: props.shortcut?.id || "", name: props.shortcut?.name || "",
title: props.shortcut?.title || "", title: props.shortcut?.title || "",
filter: props.shortcut?.filter || "", filter: props.shortcut?.filter || "",
}); });
...@@ -46,13 +45,20 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => { ...@@ -46,13 +45,20 @@ const CreateShortcutDialog: React.FC<Props> = (props: Props) => {
await shortcutServiceClient.createShortcut({ await shortcutServiceClient.createShortcut({
parent: user.name, parent: user.name,
shortcut: { shortcut: {
...shortcut, name: "", // Will be set by server
id: generateUUID(), title: shortcut.title,
filter: shortcut.filter,
}, },
}); });
toast.success("Create shortcut successfully"); toast.success("Create shortcut successfully");
} else { } else {
await shortcutServiceClient.updateShortcut({ parent: user.name, shortcut, updateMask: ["title", "filter"] }); await shortcutServiceClient.updateShortcut({
shortcut: {
...shortcut,
name: props.shortcut!.name, // Keep the original resource name
},
updateMask: ["title", "filter"],
});
toast.success("Update shortcut successfully"); toast.success("Update shortcut successfully");
} }
// Refresh shortcuts. // Refresh shortcuts.
......
...@@ -3,7 +3,6 @@ import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react"; ...@@ -3,7 +3,6 @@ import { Edit3Icon, MoreVerticalIcon, TrashIcon, PlusIcon } from "lucide-react";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { shortcutServiceClient } from "@/grpcweb"; import { shortcutServiceClient } from "@/grpcweb";
import useAsyncEffect from "@/hooks/useAsyncEffect"; import useAsyncEffect from "@/hooks/useAsyncEffect";
import useCurrentUser from "@/hooks/useCurrentUser";
import { userStore } from "@/store/v2"; import { userStore } from "@/store/v2";
import memoFilterStore from "@/store/v2/memoFilter"; import memoFilterStore from "@/store/v2/memoFilter";
import { Shortcut } from "@/types/proto/api/v1/shortcut_service"; import { Shortcut } from "@/types/proto/api/v1/shortcut_service";
...@@ -14,9 +13,15 @@ import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover"; ...@@ -14,9 +13,15 @@ import { Popover, PopoverContent, PopoverTrigger } from "../ui/Popover";
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u; const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)$/u;
// Helper function to extract shortcut ID from resource name
// Format: users/{user}/shortcuts/{shortcut}
const getShortcutId = (name: string): string => {
const parts = name.split("/");
return parts.length === 4 ? parts[3] : "";
};
const ShortcutsSection = observer(() => { const ShortcutsSection = observer(() => {
const t = useTranslate(); const t = useTranslate();
const user = useCurrentUser();
const shortcuts = userStore.state.shortcuts; const shortcuts = userStore.state.shortcuts;
useAsyncEffect(async () => { useAsyncEffect(async () => {
...@@ -26,7 +31,7 @@ const ShortcutsSection = observer(() => { ...@@ -26,7 +31,7 @@ const ShortcutsSection = observer(() => {
const handleDeleteShortcut = async (shortcut: Shortcut) => { const handleDeleteShortcut = async (shortcut: Shortcut) => {
const confirmed = window.confirm("Are you sure you want to delete this shortcut?"); const confirmed = window.confirm("Are you sure you want to delete this shortcut?");
if (confirmed) { if (confirmed) {
await shortcutServiceClient.deleteShortcut({ parent: user.name, id: shortcut.id }); await shortcutServiceClient.deleteShortcut({ name: shortcut.name });
await userStore.fetchShortcuts(); await userStore.fetchShortcuts();
} }
}; };
...@@ -41,18 +46,19 @@ const ShortcutsSection = observer(() => { ...@@ -41,18 +46,19 @@ const ShortcutsSection = observer(() => {
</div> </div>
<div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1"> <div className="w-full flex flex-row justify-start items-center relative flex-wrap gap-x-2 gap-y-1">
{shortcuts.map((shortcut) => { {shortcuts.map((shortcut) => {
const shortcutId = getShortcutId(shortcut.name);
const maybeEmoji = shortcut.title.split(" ")[0]; const maybeEmoji = shortcut.title.split(" ")[0];
const emoji = emojiRegex.test(maybeEmoji) ? maybeEmoji : undefined; const emoji = emojiRegex.test(maybeEmoji) ? maybeEmoji : undefined;
const title = emoji ? shortcut.title.replace(emoji, "") : shortcut.title; const title = emoji ? shortcut.title.replace(emoji, "") : shortcut.title;
const selected = memoFilterStore.shortcut === shortcut.id; const selected = memoFilterStore.shortcut === shortcutId;
return ( return (
<div <div
key={shortcut.id} key={shortcutId}
className="shrink-0 w-full text-sm rounded-md leading-6 flex flex-row justify-between items-center select-none gap-2 text-gray-600 dark:text-gray-400 dark:border-zinc-800" className="shrink-0 w-full text-sm rounded-md leading-6 flex flex-row justify-between items-center select-none gap-2 text-gray-600 dark:text-gray-400 dark:border-zinc-800"
> >
<span <span
className={cn("truncate cursor-pointer dark:opacity-80", selected && "text-primary font-medium")} className={cn("truncate cursor-pointer dark:opacity-80", selected && "text-primary font-medium")}
onClick={() => (selected ? memoFilterStore.setShortcut(undefined) : memoFilterStore.setShortcut(shortcut.id))} onClick={() => (selected ? memoFilterStore.setShortcut(undefined) : memoFilterStore.setShortcut(shortcutId))}
> >
{emoji && <span className="text-base mr-1">{emoji}</span>} {emoji && <span className="text-base mr-1">{emoji}</span>}
{title.trim()} {title.trim()}
......
...@@ -9,9 +9,16 @@ import memoFilterStore from "@/store/v2/memoFilter"; ...@@ -9,9 +9,16 @@ import memoFilterStore from "@/store/v2/memoFilter";
import { Direction, State } from "@/types/proto/api/v1/common"; import { Direction, State } from "@/types/proto/api/v1/common";
import { Memo } from "@/types/proto/api/v1/memo_service"; import { Memo } from "@/types/proto/api/v1/memo_service";
// Helper function to extract shortcut ID from resource name
// Format: users/{user}/shortcuts/{shortcut}
const getShortcutId = (name: string): string => {
const parts = name.split("/");
return parts.length === 4 ? parts[3] : "";
};
const Home = observer(() => { const Home = observer(() => {
const user = useCurrentUser(); const user = useCurrentUser();
const selectedShortcut = userStore.state.shortcuts.find((shortcut) => shortcut.id === memoFilterStore.shortcut); const selectedShortcut = userStore.state.shortcuts.find((shortcut) => getShortcutId(shortcut.name) === memoFilterStore.shortcut);
const memoListFilter = useMemo(() => { const memoListFilter = useMemo(() => {
const conditions = []; const conditions = [];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment