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
3b0c8759
Commit
3b0c8759
authored
Jun 16, 2025
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: webhook service
parent
c9ab03e1
Changes
10
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1474 additions
and
610 deletions
+1474
-610
webhook_service.proto
proto/api/v1/webhook_service.proto
+142
-39
webhook_service.pb.go
proto/gen/api/v1/webhook_service.pb.go
+338
-152
webhook_service.pb.gw.go
proto/gen/api/v1/webhook_service.pb.gw.go
+113
-71
webhook_service_grpc.pb.go
proto/gen/api/v1/webhook_service_grpc.pb.go
+36
-36
apidocs.swagger.yaml
proto/gen/apidocs.swagger.yaml
+237
-99
resource_name.go
server/router/api/v1/resource_name.go
+14
-0
webhook_service.go
server/router/api/v1/webhook_service.go
+106
-20
CreateWebhookDialog.tsx
web/src/components/CreateWebhookDialog.tsx
+18
-16
WebhookSection.tsx
web/src/components/Settings/WebhookSection.tsx
+9
-13
webhook_service.ts
web/src/types/proto/api/v1/webhook_service.ts
+461
-164
No files found.
proto/api/v1/webhook_service.proto
View file @
3b0c8759
...
...
@@ -2,9 +2,12 @@ syntax = "proto3";
package
memos
.
api.v1
;
import
"api/v1/common.proto"
;
import
"api/v1/memo_service.proto"
;
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"
;
...
...
@@ -12,91 +15,191 @@ import "google/protobuf/timestamp.proto";
option
go_package
=
"gen/api/v1"
;
service
WebhookService
{
// ListWebhooks returns a list of webhooks.
rpc
ListWebhooks
(
ListWebhooksRequest
)
returns
(
ListWebhooksResponse
)
{
option
(
google.api.http
)
=
{
get
:
"/api/v1/webhooks"
};
}
// GetWebhook gets a webhook by name.
rpc
GetWebhook
(
GetWebhookRequest
)
returns
(
Webhook
)
{
option
(
google.api.http
)
=
{
get
:
"/api/v1/{name=webhooks/*}"
};
option
(
google.api.method_signature
)
=
"name"
;
}
// CreateWebhook creates a new webhook.
rpc
CreateWebhook
(
CreateWebhookRequest
)
returns
(
Webhook
)
{
option
(
google.api.http
)
=
{
post
:
"/api/v1/webhooks"
body
:
"
*
"
body
:
"
webhook
"
};
option
(
google.api.method_signature
)
=
"webhook"
;
}
// GetWebhook returns a webhook by id.
rpc
GetWebhook
(
GetWebhookRequest
)
returns
(
Webhook
)
{
option
(
google.api.http
)
=
{
get
:
"/api/v1/webhooks/{id}"
};
option
(
google.api.method_signature
)
=
"id"
;
}
// ListWebhooks returns a list of webhooks.
rpc
ListWebhooks
(
ListWebhooksRequest
)
returns
(
ListWebhooksResponse
)
{
option
(
google.api.http
)
=
{
get
:
"/api/v1/webhooks"
};
}
// UpdateWebhook updates a webhook.
rpc
UpdateWebhook
(
UpdateWebhookRequest
)
returns
(
Webhook
)
{
option
(
google.api.http
)
=
{
patch
:
"/api/v1/
webhooks/{webhook.id
}"
patch
:
"/api/v1/
{webhook.name=webhooks/*
}"
body
:
"webhook"
};
option
(
google.api.method_signature
)
=
"webhook,update_mask"
;
}
// DeleteWebhook deletes a webhook by id.
// DeleteWebhook deletes a webhook.
rpc
DeleteWebhook
(
DeleteWebhookRequest
)
returns
(
google.protobuf.Empty
)
{
option
(
google.api.http
)
=
{
delete
:
"/api/v1/
webhooks/{id
}"
};
option
(
google.api.method_signature
)
=
"
id
"
;
option
(
google.api.http
)
=
{
delete
:
"/api/v1/
{name=webhooks/*
}"
};
option
(
google.api.method_signature
)
=
"
name
"
;
}
}
message
Webhook
{
int32
id
=
1
;
option
(
google.api.resource
)
=
{
type
:
"memos.api.v1/Webhook"
pattern
:
"webhooks/{webhook}"
name_field
:
"name"
singular
:
"webhook"
plural
:
"webhooks"
};
// The name of the creator.
string
creator
=
2
;
// The resource name of the webhook.
// Format: webhooks/{webhook}
string
name
=
1
[(
google.api.field_behavior
)
=
IDENTIFIER
];
google.protobuf.Timestamp
create_time
=
3
;
// Output only. The system generated unique identifier.
string
uid
=
2
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
google.protobuf.Timestamp
update_time
=
4
;
// Required. The display name of the webhook.
string
display_name
=
3
[(
google.api.field_behavior
)
=
REQUIRED
];
string
name
=
5
;
// Required. The target URL for the webhook.
string
url
=
4
[(
google.api.field_behavior
)
=
REQUIRED
];
string
url
=
6
;
}
// Output only. The resource name of the creator.
// Format: users/{user}
string
creator
=
5
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
message
CreateWebhookRequest
{
string
name
=
1
;
// The state of the webhook.
State
state
=
6
[(
google.api.field_behavior
)
=
REQUIRED
]
;
string
url
=
2
;
}
// Output only. The creation timestamp.
google.protobuf.Timestamp
create_time
=
7
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
message
GetWebhookRequest
{
int32
id
=
1
;
// Output only. The last update timestamp.
google.protobuf.Timestamp
update_time
=
8
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
// Output only. The etag for this resource.
string
etag
=
9
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
}
message
ListWebhooksRequest
{
// The name of the creator.
string
creator
=
2
;
// Optional. The maximum number of webhooks to return.
// The service may return fewer than this value.
// If unspecified, at most 50 webhooks will be returned.
// The maximum value is 1000; values above 1000 will be coerced to 1000.
int32
page_size
=
1
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. A page token, received from a previous `ListWebhooks` call.
// Provide this to retrieve the subsequent page.
string
page_token
=
2
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. Filter to apply to the list results.
// Example: "state=ACTIVE" or "creator=users/123"
// Supported operators: =, !=, <, <=, >, >=, :
// Supported fields: display_name, url, creator, state, create_time, update_time
string
filter
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. The order to sort results by.
// Example: "create_time desc" or "display_name asc"
string
order_by
=
4
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. If true, show deleted webhooks in the response.
bool
show_deleted
=
5
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
ListWebhooksResponse
{
// The list of webhooks.
repeated
Webhook
webhooks
=
1
;
// 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 webhooks (may be approximate).
int32
total_size
=
3
;
}
message
GetWebhookRequest
{
// Required. The resource name of the webhook.
// Format: webhooks/{webhook}
string
name
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/Webhook"
}
];
// Optional. The fields to return in the response.
// If not specified, all fields are returned.
google.protobuf.FieldMask
read_mask
=
2
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
CreateWebhookRequest
{
// Required. The webhook to create.
Webhook
webhook
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.field_behavior
)
=
INPUT_ONLY
];
// Optional. The webhook ID to use for this webhook.
// If empty, a unique ID will be generated.
// Must match the pattern [a-z0-9-]+
string
webhook_id
=
2
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. If set, validate the request but don't actually create the webhook.
bool
validate_only
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
// Optional. An idempotency token that can be used to ensure that multiple
// requests to create a webhook have the same result.
string
request_id
=
4
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
UpdateWebhookRequest
{
Webhook
webhook
=
1
;
// Required. The webhook to update.
Webhook
webhook
=
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 sensitive fields.
bool
allow_missing
=
3
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
DeleteWebhookRequest
{
int32
id
=
1
;
// Required. The resource name of the webhook to delete.
// Format: webhooks/{webhook}
string
name
=
1
[
(
google.api.field_behavior
)
=
REQUIRED
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/Webhook"
}
];
// Optional. If set to true, the webhook will be deleted even if it has associated data.
bool
force
=
2
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
message
WebhookRequestPayload
{
string
url
=
1
;
// The target URL for the webhook request.
string
url
=
1
[(
google.api.field_behavior
)
=
REQUIRED
];
string
activity_type
=
2
;
// The type of activity that triggered this webhook.
string
activity_type
=
2
[(
google.api.field_behavior
)
=
REQUIRED
];
// The name of the creator.
// The
resource
name of the creator.
// Format: users/{user}
string
creator
=
3
;
string
creator
=
3
[
(
google.api.field_behavior
)
=
OUTPUT_ONLY
,
(
google.api.resource_reference
)
=
{
type
:
"memos.api.v1/User"
}
];
google.protobuf.Timestamp
create_time
=
4
;
// The creation timestamp of the activity.
google.protobuf.Timestamp
create_time
=
4
[(
google.api.field_behavior
)
=
OUTPUT_ONLY
];
Memo
memo
=
5
;
// The memo that triggered this webhook (if applicable).
Memo
memo
=
5
[(
google.api.field_behavior
)
=
OPTIONAL
];
}
proto/gen/api/v1/webhook_service.pb.go
View file @
3b0c8759
This diff is collapsed.
Click to expand it.
proto/gen/api/v1/webhook_service.pb.gw.go
View file @
3b0c8759
This diff is collapsed.
Click to expand it.
proto/gen/api/v1/webhook_service_grpc.pb.go
View file @
3b0c8759
...
...
@@ -20,9 +20,9 @@ import (
const
_
=
grpc
.
SupportPackageIsVersion9
const
(
WebhookService_CreateWebhook_FullMethodName
=
"/memos.api.v1.WebhookService/CreateWebhook"
WebhookService_GetWebhook_FullMethodName
=
"/memos.api.v1.WebhookService/GetWebhook"
WebhookService_ListWebhooks_FullMethodName
=
"/memos.api.v1.WebhookService/ListWebhooks"
WebhookService_GetWebhook_FullMethodName
=
"/memos.api.v1.WebhookService/GetWebhook"
WebhookService_CreateWebhook_FullMethodName
=
"/memos.api.v1.WebhookService/CreateWebhook"
WebhookService_UpdateWebhook_FullMethodName
=
"/memos.api.v1.WebhookService/UpdateWebhook"
WebhookService_DeleteWebhook_FullMethodName
=
"/memos.api.v1.WebhookService/DeleteWebhook"
)
...
...
@@ -31,15 +31,15 @@ const (
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type
WebhookServiceClient
interface
{
// CreateWebhook creates a new webhook.
CreateWebhook
(
ctx
context
.
Context
,
in
*
CreateWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
// GetWebhook returns a webhook by id.
GetWebhook
(
ctx
context
.
Context
,
in
*
GetWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
// ListWebhooks returns a list of webhooks.
ListWebhooks
(
ctx
context
.
Context
,
in
*
ListWebhooksRequest
,
opts
...
grpc
.
CallOption
)
(
*
ListWebhooksResponse
,
error
)
// GetWebhook gets a webhook by name.
GetWebhook
(
ctx
context
.
Context
,
in
*
GetWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
// CreateWebhook creates a new webhook.
CreateWebhook
(
ctx
context
.
Context
,
in
*
CreateWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
// UpdateWebhook updates a webhook.
UpdateWebhook
(
ctx
context
.
Context
,
in
*
UpdateWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
// DeleteWebhook deletes a webhook
by id
.
// DeleteWebhook deletes a webhook.
DeleteWebhook
(
ctx
context
.
Context
,
in
*
DeleteWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
emptypb
.
Empty
,
error
)
}
...
...
@@ -51,10 +51,10 @@ func NewWebhookServiceClient(cc grpc.ClientConnInterface) WebhookServiceClient {
return
&
webhookServiceClient
{
cc
}
}
func
(
c
*
webhookServiceClient
)
CreateWebhook
(
ctx
context
.
Context
,
in
*
CreateWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
{
func
(
c
*
webhookServiceClient
)
ListWebhooks
(
ctx
context
.
Context
,
in
*
ListWebhooksRequest
,
opts
...
grpc
.
CallOption
)
(
*
ListWebhooksResponse
,
error
)
{
cOpts
:=
append
([]
grpc
.
CallOption
{
grpc
.
StaticMethod
()},
opts
...
)
out
:=
new
(
Webhook
)
err
:=
c
.
cc
.
Invoke
(
ctx
,
WebhookService_
CreateWebhook
_FullMethodName
,
in
,
out
,
cOpts
...
)
out
:=
new
(
ListWebhooksResponse
)
err
:=
c
.
cc
.
Invoke
(
ctx
,
WebhookService_
ListWebhooks
_FullMethodName
,
in
,
out
,
cOpts
...
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -71,10 +71,10 @@ func (c *webhookServiceClient) GetWebhook(ctx context.Context, in *GetWebhookReq
return
out
,
nil
}
func
(
c
*
webhookServiceClient
)
ListWebhooks
(
ctx
context
.
Context
,
in
*
ListWebhooksRequest
,
opts
...
grpc
.
CallOption
)
(
*
ListWebhooksResponse
,
error
)
{
func
(
c
*
webhookServiceClient
)
CreateWebhook
(
ctx
context
.
Context
,
in
*
CreateWebhookRequest
,
opts
...
grpc
.
CallOption
)
(
*
Webhook
,
error
)
{
cOpts
:=
append
([]
grpc
.
CallOption
{
grpc
.
StaticMethod
()},
opts
...
)
out
:=
new
(
ListWebhooksResponse
)
err
:=
c
.
cc
.
Invoke
(
ctx
,
WebhookService_
ListWebhooks
_FullMethodName
,
in
,
out
,
cOpts
...
)
out
:=
new
(
Webhook
)
err
:=
c
.
cc
.
Invoke
(
ctx
,
WebhookService_
CreateWebhook
_FullMethodName
,
in
,
out
,
cOpts
...
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -105,15 +105,15 @@ func (c *webhookServiceClient) DeleteWebhook(ctx context.Context, in *DeleteWebh
// All implementations must embed UnimplementedWebhookServiceServer
// for forward compatibility.
type
WebhookServiceServer
interface
{
// CreateWebhook creates a new webhook.
CreateWebhook
(
context
.
Context
,
*
CreateWebhookRequest
)
(
*
Webhook
,
error
)
// GetWebhook returns a webhook by id.
GetWebhook
(
context
.
Context
,
*
GetWebhookRequest
)
(
*
Webhook
,
error
)
// ListWebhooks returns a list of webhooks.
ListWebhooks
(
context
.
Context
,
*
ListWebhooksRequest
)
(
*
ListWebhooksResponse
,
error
)
// GetWebhook gets a webhook by name.
GetWebhook
(
context
.
Context
,
*
GetWebhookRequest
)
(
*
Webhook
,
error
)
// CreateWebhook creates a new webhook.
CreateWebhook
(
context
.
Context
,
*
CreateWebhookRequest
)
(
*
Webhook
,
error
)
// UpdateWebhook updates a webhook.
UpdateWebhook
(
context
.
Context
,
*
UpdateWebhookRequest
)
(
*
Webhook
,
error
)
// DeleteWebhook deletes a webhook
by id
.
// DeleteWebhook deletes a webhook.
DeleteWebhook
(
context
.
Context
,
*
DeleteWebhookRequest
)
(
*
emptypb
.
Empty
,
error
)
mustEmbedUnimplementedWebhookServiceServer
()
}
...
...
@@ -125,14 +125,14 @@ type WebhookServiceServer interface {
// pointer dereference when methods are called.
type
UnimplementedWebhookServiceServer
struct
{}
func
(
UnimplementedWebhookServiceServer
)
CreateWebhook
(
context
.
Context
,
*
CreateWebhookRequest
)
(
*
Webhook
,
error
)
{
return
nil
,
status
.
Errorf
(
codes
.
Unimplemented
,
"method
CreateWebhook
not implemented"
)
func
(
UnimplementedWebhookServiceServer
)
ListWebhooks
(
context
.
Context
,
*
ListWebhooksRequest
)
(
*
ListWebhooksResponse
,
error
)
{
return
nil
,
status
.
Errorf
(
codes
.
Unimplemented
,
"method
ListWebhooks
not implemented"
)
}
func
(
UnimplementedWebhookServiceServer
)
GetWebhook
(
context
.
Context
,
*
GetWebhookRequest
)
(
*
Webhook
,
error
)
{
return
nil
,
status
.
Errorf
(
codes
.
Unimplemented
,
"method GetWebhook not implemented"
)
}
func
(
UnimplementedWebhookServiceServer
)
ListWebhooks
(
context
.
Context
,
*
ListWebhooksRequest
)
(
*
ListWebhooksResponse
,
error
)
{
return
nil
,
status
.
Errorf
(
codes
.
Unimplemented
,
"method
ListWebhooks
not implemented"
)
func
(
UnimplementedWebhookServiceServer
)
CreateWebhook
(
context
.
Context
,
*
CreateWebhookRequest
)
(
*
Webhook
,
error
)
{
return
nil
,
status
.
Errorf
(
codes
.
Unimplemented
,
"method
CreateWebhook
not implemented"
)
}
func
(
UnimplementedWebhookServiceServer
)
UpdateWebhook
(
context
.
Context
,
*
UpdateWebhookRequest
)
(
*
Webhook
,
error
)
{
return
nil
,
status
.
Errorf
(
codes
.
Unimplemented
,
"method UpdateWebhook not implemented"
)
...
...
@@ -161,20 +161,20 @@ func RegisterWebhookServiceServer(s grpc.ServiceRegistrar, srv WebhookServiceSer
s
.
RegisterService
(
&
WebhookService_ServiceDesc
,
srv
)
}
func
_WebhookService_
CreateWebhook
_Handler
(
srv
interface
{},
ctx
context
.
Context
,
dec
func
(
interface
{})
error
,
interceptor
grpc
.
UnaryServerInterceptor
)
(
interface
{},
error
)
{
in
:=
new
(
CreateWebhook
Request
)
func
_WebhookService_
ListWebhooks
_Handler
(
srv
interface
{},
ctx
context
.
Context
,
dec
func
(
interface
{})
error
,
interceptor
grpc
.
UnaryServerInterceptor
)
(
interface
{},
error
)
{
in
:=
new
(
ListWebhooks
Request
)
if
err
:=
dec
(
in
);
err
!=
nil
{
return
nil
,
err
}
if
interceptor
==
nil
{
return
srv
.
(
WebhookServiceServer
)
.
CreateWebhook
(
ctx
,
in
)
return
srv
.
(
WebhookServiceServer
)
.
ListWebhooks
(
ctx
,
in
)
}
info
:=
&
grpc
.
UnaryServerInfo
{
Server
:
srv
,
FullMethod
:
WebhookService_
CreateWebhook
_FullMethodName
,
FullMethod
:
WebhookService_
ListWebhooks
_FullMethodName
,
}
handler
:=
func
(
ctx
context
.
Context
,
req
interface
{})
(
interface
{},
error
)
{
return
srv
.
(
WebhookServiceServer
)
.
CreateWebhook
(
ctx
,
req
.
(
*
CreateWebhook
Request
))
return
srv
.
(
WebhookServiceServer
)
.
ListWebhooks
(
ctx
,
req
.
(
*
ListWebhooks
Request
))
}
return
interceptor
(
ctx
,
in
,
info
,
handler
)
}
...
...
@@ -197,20 +197,20 @@ func _WebhookService_GetWebhook_Handler(srv interface{}, ctx context.Context, de
return
interceptor
(
ctx
,
in
,
info
,
handler
)
}
func
_WebhookService_
ListWebhooks
_Handler
(
srv
interface
{},
ctx
context
.
Context
,
dec
func
(
interface
{})
error
,
interceptor
grpc
.
UnaryServerInterceptor
)
(
interface
{},
error
)
{
in
:=
new
(
ListWebhooks
Request
)
func
_WebhookService_
CreateWebhook
_Handler
(
srv
interface
{},
ctx
context
.
Context
,
dec
func
(
interface
{})
error
,
interceptor
grpc
.
UnaryServerInterceptor
)
(
interface
{},
error
)
{
in
:=
new
(
CreateWebhook
Request
)
if
err
:=
dec
(
in
);
err
!=
nil
{
return
nil
,
err
}
if
interceptor
==
nil
{
return
srv
.
(
WebhookServiceServer
)
.
ListWebhooks
(
ctx
,
in
)
return
srv
.
(
WebhookServiceServer
)
.
CreateWebhook
(
ctx
,
in
)
}
info
:=
&
grpc
.
UnaryServerInfo
{
Server
:
srv
,
FullMethod
:
WebhookService_
ListWebhooks
_FullMethodName
,
FullMethod
:
WebhookService_
CreateWebhook
_FullMethodName
,
}
handler
:=
func
(
ctx
context
.
Context
,
req
interface
{})
(
interface
{},
error
)
{
return
srv
.
(
WebhookServiceServer
)
.
ListWebhooks
(
ctx
,
req
.
(
*
ListWebhooks
Request
))
return
srv
.
(
WebhookServiceServer
)
.
CreateWebhook
(
ctx
,
req
.
(
*
CreateWebhook
Request
))
}
return
interceptor
(
ctx
,
in
,
info
,
handler
)
}
...
...
@@ -259,16 +259,16 @@ var WebhookService_ServiceDesc = grpc.ServiceDesc{
HandlerType
:
(
*
WebhookServiceServer
)(
nil
),
Methods
:
[]
grpc
.
MethodDesc
{
{
MethodName
:
"
CreateWebhook
"
,
Handler
:
_WebhookService_
CreateWebhook
_Handler
,
MethodName
:
"
ListWebhooks
"
,
Handler
:
_WebhookService_
ListWebhooks
_Handler
,
},
{
MethodName
:
"GetWebhook"
,
Handler
:
_WebhookService_GetWebhook_Handler
,
},
{
MethodName
:
"
ListWebhooks
"
,
Handler
:
_WebhookService_
ListWebhooks
_Handler
,
MethodName
:
"
CreateWebhook
"
,
Handler
:
_WebhookService_
CreateWebhook
_Handler
,
},
{
MethodName
:
"UpdateWebhook"
,
...
...
proto/gen/apidocs.swagger.yaml
View file @
3b0c8759
This diff is collapsed.
Click to expand it.
server/router/api/v1/resource_name.go
View file @
3b0c8759
...
...
@@ -17,6 +17,7 @@ const (
InboxNamePrefix
=
"inboxes/"
IdentityProviderNamePrefix
=
"identityProviders/"
ActivityNamePrefix
=
"activities/"
WebhookNamePrefix
=
"webhooks/"
)
// GetNameParentTokens returns the tokens from a resource name.
...
...
@@ -117,3 +118,16 @@ func ExtractActivityIDFromName(name string) (int32, error) {
}
return
id
,
nil
}
// ExtractWebhookIDFromName returns the webhook ID from a resource name.
func
ExtractWebhookIDFromName
(
name
string
)
(
int32
,
error
)
{
tokens
,
err
:=
GetNameParentTokens
(
name
,
WebhookNamePrefix
)
if
err
!=
nil
{
return
0
,
err
}
id
,
err
:=
util
.
ConvertStringToInt32
(
tokens
[
0
])
if
err
!=
nil
{
return
0
,
errors
.
Errorf
(
"invalid webhook ID %q"
,
tokens
[
0
])
}
return
id
,
nil
}
server/router/api/v1/webhook_service.go
View file @
3b0c8759
...
...
@@ -2,6 +2,7 @@ package v1
import
(
"context"
"crypto/md5"
"fmt"
"strings"
"time"
...
...
@@ -21,10 +22,21 @@ func (s *APIV1Service) CreateWebhook(ctx context.Context, request *v1pb.CreateWe
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get user: %v"
,
err
)
}
// TODO: Handle webhook_id, validate_only, and request_id fields
if
request
.
ValidateOnly
{
// Perform validation checks without actually creating the webhook
return
&
v1pb
.
Webhook
{
DisplayName
:
request
.
Webhook
.
DisplayName
,
Url
:
request
.
Webhook
.
Url
,
Creator
:
fmt
.
Sprintf
(
"users/%d"
,
currentUser
.
ID
),
State
:
request
.
Webhook
.
State
,
},
nil
}
webhook
,
err
:=
s
.
Store
.
CreateWebhook
(
ctx
,
&
store
.
Webhook
{
CreatorID
:
currentUser
.
ID
,
Name
:
request
.
Name
,
URL
:
strings
.
TrimSpace
(
request
.
Url
),
Name
:
request
.
Webhook
.
Display
Name
,
URL
:
strings
.
TrimSpace
(
request
.
Webhook
.
Url
),
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to create webhook, error: %+v"
,
err
)
...
...
@@ -32,21 +44,24 @@ func (s *APIV1Service) CreateWebhook(ctx context.Context, request *v1pb.CreateWe
return
convertWebhookFromStore
(
webhook
),
nil
}
func
(
s
*
APIV1Service
)
ListWebhooks
(
ctx
context
.
Context
,
request
*
v1pb
.
ListWebhooksRequest
)
(
*
v1pb
.
ListWebhooksResponse
,
error
)
{
c
reatorID
,
err
:=
ExtractUserIDFromName
(
request
.
Creator
)
func
(
s
*
APIV1Service
)
ListWebhooks
(
ctx
context
.
Context
,
_
*
v1pb
.
ListWebhooksRequest
)
(
*
v1pb
.
ListWebhooksResponse
,
error
)
{
c
urrentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
In
validArgument
,
"invalid creator name
: %v"
,
err
)
return
nil
,
status
.
Errorf
(
codes
.
In
ternal
,
"failed to get user
: %v"
,
err
)
}
// TODO: Implement proper filtering, ordering, and pagination
// For now, list webhooks for the current user
webhooks
,
err
:=
s
.
Store
.
ListWebhooks
(
ctx
,
&
store
.
FindWebhook
{
CreatorID
:
&
c
reator
ID
,
CreatorID
:
&
c
urrentUser
.
ID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to list webhooks, error: %+v"
,
err
)
}
response
:=
&
v1pb
.
ListWebhooksResponse
{
Webhooks
:
[]
*
v1pb
.
Webhook
{},
Webhooks
:
[]
*
v1pb
.
Webhook
{},
TotalSize
:
int32
(
len
(
webhooks
)),
}
for
_
,
webhook
:=
range
webhooks
{
response
.
Webhooks
=
append
(
response
.
Webhooks
,
convertWebhookFromStore
(
webhook
))
...
...
@@ -55,13 +70,18 @@ func (s *APIV1Service) ListWebhooks(ctx context.Context, request *v1pb.ListWebho
}
func
(
s
*
APIV1Service
)
GetWebhook
(
ctx
context
.
Context
,
request
*
v1pb
.
GetWebhookRequest
)
(
*
v1pb
.
Webhook
,
error
)
{
webhookID
,
err
:=
ExtractWebhookIDFromName
(
request
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid webhook name: %v"
,
err
)
}
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get user: %v"
,
err
)
}
webhook
,
err
:=
s
.
Store
.
GetWebhook
(
ctx
,
&
store
.
FindWebhook
{
ID
:
&
request
.
Id
,
ID
:
&
webhookID
,
CreatorID
:
&
currentUser
.
ID
,
})
if
err
!=
nil
{
...
...
@@ -70,7 +90,12 @@ func (s *APIV1Service) GetWebhook(ctx context.Context, request *v1pb.GetWebhookR
if
webhook
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"webhook not found"
)
}
return
convertWebhookFromStore
(
webhook
),
nil
webhookPb
:=
convertWebhookFromStore
(
webhook
)
// TODO: Implement read_mask field filtering
return
webhookPb
,
nil
}
func
(
s
*
APIV1Service
)
UpdateWebhook
(
ctx
context
.
Context
,
request
*
v1pb
.
UpdateWebhookRequest
)
(
*
v1pb
.
Webhook
,
error
)
{
...
...
@@ -78,13 +103,43 @@ func (s *APIV1Service) UpdateWebhook(ctx context.Context, request *v1pb.UpdateWe
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"update_mask is required"
)
}
update
:=
&
store
.
UpdateWebhook
{}
webhookID
,
err
:=
ExtractWebhookIDFromName
(
request
.
Webhook
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid webhook name: %v"
,
err
)
}
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get user: %v"
,
err
)
}
// Check if webhook exists and user has permission
existingWebhook
,
err
:=
s
.
Store
.
GetWebhook
(
ctx
,
&
store
.
FindWebhook
{
ID
:
&
webhookID
,
CreatorID
:
&
currentUser
.
ID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get webhook: %v"
,
err
)
}
if
existingWebhook
==
nil
{
if
request
.
AllowMissing
{
// Could create webhook if missing, but for now return not found
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"webhook not found"
)
}
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"webhook not found"
)
}
update
:=
&
store
.
UpdateWebhook
{
ID
:
webhookID
,
}
for
_
,
field
:=
range
request
.
UpdateMask
.
Paths
{
switch
field
{
case
"name"
:
update
.
Name
=
&
request
.
Webhook
.
Name
case
"
display_
name"
:
update
.
Name
=
&
request
.
Webhook
.
Display
Name
case
"url"
:
update
.
URL
=
&
request
.
Webhook
.
Url
default
:
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid update path: %s"
,
field
)
}
}
...
...
@@ -96,8 +151,32 @@ func (s *APIV1Service) UpdateWebhook(ctx context.Context, request *v1pb.UpdateWe
}
func
(
s
*
APIV1Service
)
DeleteWebhook
(
ctx
context
.
Context
,
request
*
v1pb
.
DeleteWebhookRequest
)
(
*
emptypb
.
Empty
,
error
)
{
err
:=
s
.
Store
.
DeleteWebhook
(
ctx
,
&
store
.
DeleteWebhook
{
ID
:
request
.
Id
,
webhookID
,
err
:=
ExtractWebhookIDFromName
(
request
.
Name
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid webhook name: %v"
,
err
)
}
currentUser
,
err
:=
s
.
GetCurrentUser
(
ctx
)
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get user: %v"
,
err
)
}
// Check if webhook exists and user has permission
webhook
,
err
:=
s
.
Store
.
GetWebhook
(
ctx
,
&
store
.
FindWebhook
{
ID
:
&
webhookID
,
CreatorID
:
&
currentUser
.
ID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to get webhook: %v"
,
err
)
}
if
webhook
==
nil
{
return
nil
,
status
.
Errorf
(
codes
.
NotFound
,
"webhook not found"
)
}
// TODO: Handle force field properly
err
=
s
.
Store
.
DeleteWebhook
(
ctx
,
&
store
.
DeleteWebhook
{
ID
:
webhookID
,
})
if
err
!=
nil
{
return
nil
,
status
.
Errorf
(
codes
.
Internal
,
"failed to delete webhook, error: %+v"
,
err
)
...
...
@@ -106,12 +185,19 @@ func (s *APIV1Service) DeleteWebhook(ctx context.Context, request *v1pb.DeleteWe
}
func
convertWebhookFromStore
(
webhook
*
store
.
Webhook
)
*
v1pb
.
Webhook
{
// Generate etag using MD5 hash of webhook data
etag
:=
fmt
.
Sprintf
(
"%x"
,
md5
.
Sum
([]
byte
(
fmt
.
Sprintf
(
"%d-%d-%s-%s"
,
webhook
.
ID
,
webhook
.
UpdatedTs
,
webhook
.
Name
,
webhook
.
URL
))))
return
&
v1pb
.
Webhook
{
Id
:
webhook
.
ID
,
CreateTime
:
timestamppb
.
New
(
time
.
Unix
(
webhook
.
CreatedTs
,
0
)),
UpdateTime
:
timestamppb
.
New
(
time
.
Unix
(
webhook
.
UpdatedTs
,
0
)),
Creator
:
fmt
.
Sprintf
(
"%s%d"
,
UserNamePrefix
,
webhook
.
CreatorID
),
Name
:
webhook
.
Name
,
Url
:
webhook
.
URL
,
Name
:
fmt
.
Sprintf
(
"webhooks/%d"
,
webhook
.
ID
),
Uid
:
fmt
.
Sprintf
(
"%d"
,
webhook
.
ID
),
DisplayName
:
webhook
.
Name
,
Url
:
webhook
.
URL
,
Creator
:
fmt
.
Sprintf
(
"users/%d"
,
webhook
.
CreatorID
),
State
:
v1pb
.
State_NORMAL
,
// Default to NORMAL state for webhooks
CreateTime
:
timestamppb
.
New
(
time
.
Unix
(
webhook
.
CreatedTs
,
0
)),
UpdateTime
:
timestamppb
.
New
(
time
.
Unix
(
webhook
.
UpdatedTs
,
0
)),
Etag
:
etag
,
}
}
web/src/components/CreateWebhookDialog.tsx
View file @
3b0c8759
...
...
@@ -8,34 +8,34 @@ import { useTranslate } from "@/utils/i18n";
import
{
generateDialog
}
from
"./Dialog"
;
interface
Props
extends
DialogProps
{
webhook
Id
?:
number
;
webhook
Name
?:
string
;
onConfirm
:
()
=>
void
;
}
interface
State
{
n
ame
:
string
;
displayN
ame
:
string
;
url
:
string
;
}
const
CreateWebhookDialog
:
React
.
FC
<
Props
>
=
(
props
:
Props
)
=>
{
const
{
webhook
Id
,
destroy
,
onConfirm
}
=
props
;
const
{
webhook
Name
,
destroy
,
onConfirm
}
=
props
;
const
t
=
useTranslate
();
const
[
state
,
setState
]
=
useState
({
n
ame
:
""
,
displayN
ame
:
""
,
url
:
""
,
});
const
requestState
=
useLoading
(
false
);
const
isCreating
=
webhook
Id
===
undefined
;
const
isCreating
=
webhook
Name
===
undefined
;
useEffect
(()
=>
{
if
(
webhook
Id
)
{
if
(
webhook
Name
)
{
webhookServiceClient
.
getWebhook
({
id
:
webhookId
,
name
:
webhookName
,
})
.
then
((
webhook
)
=>
{
setState
({
name
:
webhook
.
n
ame
,
displayName
:
webhook
.
displayN
ame
,
url
:
webhook
.
url
,
});
});
...
...
@@ -51,7 +51,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
const
handleTitleInputChange
=
(
e
:
React
.
ChangeEvent
<
HTMLInputElement
>
)
=>
{
setPartialState
({
n
ame
:
e
.
target
.
value
,
displayN
ame
:
e
.
target
.
value
,
});
};
...
...
@@ -62,7 +62,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
};
const
handleSaveBtnClick
=
async
()
=>
{
if
(
!
state
.
n
ame
||
!
state
.
url
)
{
if
(
!
state
.
displayN
ame
||
!
state
.
url
)
{
toast
.
error
(
t
(
"message.fill-all-required-fields"
));
return
;
}
...
...
@@ -70,17 +70,19 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
try
{
if
(
isCreating
)
{
await
webhookServiceClient
.
createWebhook
({
name
:
state
.
name
,
url
:
state
.
url
,
webhook
:
{
displayName
:
state
.
displayName
,
url
:
state
.
url
,
},
});
}
else
{
await
webhookServiceClient
.
updateWebhook
({
webhook
:
{
id
:
webhookId
,
name
:
state
.
n
ame
,
name
:
webhookName
,
displayName
:
state
.
displayN
ame
,
url
:
state
.
url
,
},
updateMask
:
[
"name"
,
"url"
],
updateMask
:
[
"
display_
name"
,
"url"
],
});
}
...
...
@@ -112,7 +114,7 @@ const CreateWebhookDialog: React.FC<Props> = (props: Props) => {
className=
"w-full"
type=
"text"
placeholder=
{
t
(
"setting.webhook-section.create-dialog.an-easy-to-remember-name"
)
}
value=
{
state
.
n
ame
}
value=
{
state
.
displayN
ame
}
onChange=
{
handleTitleInputChange
}
/>
</
div
>
...
...
web/src/components/Settings/WebhookSection.tsx
View file @
3b0c8759
...
...
@@ -3,39 +3,35 @@ import { ExternalLinkIcon, TrashIcon } from "lucide-react";
import
{
useEffect
,
useState
}
from
"react"
;
import
{
Link
}
from
"react-router-dom"
;
import
{
webhookServiceClient
}
from
"@/grpcweb"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
{
Webhook
}
from
"@/types/proto/api/v1/webhook_service"
;
import
{
useTranslate
}
from
"@/utils/i18n"
;
import
showCreateWebhookDialog
from
"../CreateWebhookDialog"
;
const
listWebhooks
=
async
(
user
:
string
)
=>
{
const
{
webhooks
}
=
await
webhookServiceClient
.
listWebhooks
({
creator
:
user
,
});
const
listWebhooks
=
async
()
=>
{
const
{
webhooks
}
=
await
webhookServiceClient
.
listWebhooks
({});
return
webhooks
;
};
const
WebhookSection
=
()
=>
{
const
t
=
useTranslate
();
const
currentUser
=
useCurrentUser
();
const
[
webhooks
,
setWebhooks
]
=
useState
<
Webhook
[]
>
([]);
useEffect
(()
=>
{
listWebhooks
(
currentUser
.
name
).
then
((
webhooks
)
=>
{
listWebhooks
().
then
((
webhooks
)
=>
{
setWebhooks
(
webhooks
);
});
},
[]);
const
handleCreateAccessTokenDialogConfirm
=
async
()
=>
{
const
webhooks
=
await
listWebhooks
(
currentUser
.
name
);
const
webhooks
=
await
listWebhooks
();
setWebhooks
(
webhooks
);
};
const
handleDeleteWebhook
=
async
(
webhook
:
Webhook
)
=>
{
const
confirmed
=
window
.
confirm
(
`Are you sure to delete webhook \`
${
webhook
.
n
ame
}
\`? You cannot undo this action.`
);
const
confirmed
=
window
.
confirm
(
`Are you sure to delete webhook \`
${
webhook
.
displayN
ame
}
\`? You cannot undo this action.`
);
if
(
confirmed
)
{
await
webhookServiceClient
.
deleteWebhook
({
id
:
webhook
.
id
});
setWebhooks
(
webhooks
.
filter
((
item
)
=>
item
.
id
!==
webhook
.
id
));
await
webhookServiceClient
.
deleteWebhook
({
name
:
webhook
.
name
});
setWebhooks
(
webhooks
.
filter
((
item
)
=>
item
.
name
!==
webhook
.
name
));
}
};
...
...
@@ -77,8 +73,8 @@ const WebhookSection = () => {
</
thead
>
<
tbody
className=
"divide-y divide-gray-200 dark:divide-gray-500"
>
{
webhooks
.
map
((
webhook
)
=>
(
<
tr
key=
{
webhook
.
id
}
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400"
>
{
webhook
.
n
ame
}
</
td
>
<
tr
key=
{
webhook
.
name
}
>
<
td
className=
"whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400"
>
{
webhook
.
displayN
ame
}
</
td
>
<
td
className=
"max-w-[200px] px-3 py-2 text-sm text-gray-900 dark:text-gray-400 truncate"
title=
{
webhook
.
url
}
>
{
webhook
.
url
}
</
td
>
...
...
web/src/types/proto/api/v1/webhook_service.ts
View file @
3b0c8759
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