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
bc965f6a
Commit
bc965f6a
authored
Nov 25, 2023
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
chore: implement webhook dispatch in api v1
parent
db95b94c
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
202 additions
and
1 deletion
+202
-1
memo.go
api/v1/memo.go
+85
-1
memo_relation.go
api/v1/memo_relation.go
+4
-0
webhook.go
plugin/webhook/webhook.go
+112
-0
webhook_test.go
plugin/webhook/webhook_test.go
+1
-0
No files found.
api/v1/memo.go
View file @
bc965f6a
...
...
@@ -14,6 +14,7 @@ import (
"github.com/usememos/memos/internal/log"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/plugin/webhook"
storepb
"github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/server/service/metric"
"github.com/usememos/memos/store"
...
...
@@ -392,7 +393,7 @@ func (s *APIV1Service) CreateMemo(c echo.Context) error {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
//
send notification by telegram bot if memo is not Private
//
Send notification to telegram if memo is not private.
if
memoResponse
.
Visibility
!=
Private
{
// fetch all telegram UserID
userSettings
,
err
:=
s
.
Store
.
ListUserSettings
(
ctx
,
&
store
.
FindUserSetting
{
Key
:
UserSettingTelegramUserIDKey
.
String
()})
...
...
@@ -423,6 +424,11 @@ func (s *APIV1Service) CreateMemo(c echo.Context) error {
}
}
}
// Try to dispatch webhook when memo is created.
if
err
:=
s
.
DispatchMemoCreatedWebhook
(
ctx
,
memoResponse
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to dispatch memo created webhook"
)
.
SetInternal
(
err
)
}
metric
.
Enqueue
(
"memo create"
)
return
c
.
JSON
(
http
.
StatusOK
,
memoResponse
)
}
...
...
@@ -795,6 +801,11 @@ func (s *APIV1Service) UpdateMemo(c echo.Context) error {
if
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to compose memo response"
)
.
SetInternal
(
err
)
}
// Try to dispatch webhook when memo is created.
if
err
:=
s
.
DispatchMemoCreatedWebhook
(
ctx
,
memoResponse
);
err
!=
nil
{
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"Failed to dispatch memo created webhook"
)
.
SetInternal
(
err
)
}
return
c
.
JSON
(
http
.
StatusOK
,
memoResponse
)
}
...
...
@@ -946,3 +957,76 @@ func getIDListDiff(oldList, newList []int32) (addedList, removedList []int32) {
}
return
addedList
,
removedList
}
// DispatchMemoCreatedWebhook dispatches webhook when memo is created.
func
(
s
*
APIV1Service
)
DispatchMemoCreatedWebhook
(
ctx
context
.
Context
,
memo
*
Memo
)
error
{
return
s
.
dispatchMemoRelatedWebhook
(
ctx
,
memo
,
"memos.memo.created"
)
}
// DispatchMemoUpdatedWebhook dispatches webhook when memo is updated.
func
(
s
*
APIV1Service
)
DispatchMemoUpdatedWebhook
(
ctx
context
.
Context
,
memo
*
Memo
)
error
{
return
s
.
dispatchMemoRelatedWebhook
(
ctx
,
memo
,
"memos.memo.updated"
)
}
func
(
s
*
APIV1Service
)
dispatchMemoRelatedWebhook
(
ctx
context
.
Context
,
memo
*
Memo
,
activityType
string
)
error
{
webhooks
,
err
:=
s
.
Store
.
ListWebhooks
(
ctx
,
&
store
.
FindWebhook
{
CreatorID
:
&
memo
.
CreatorID
,
})
if
err
!=
nil
{
return
err
}
for
_
,
wb
:=
range
webhooks
{
payload
:=
convertMemoToWebhookPayload
(
memo
)
payload
.
ActivityType
=
activityType
payload
.
URL
=
wb
.
Url
err
:=
webhook
.
Post
(
*
payload
)
if
err
!=
nil
{
return
errors
.
Wrap
(
err
,
"failed to post webhook"
)
}
}
return
nil
}
func
convertMemoToWebhookPayload
(
memo
*
Memo
)
*
webhook
.
WebhookPayload
{
return
&
webhook
.
WebhookPayload
{
CreatorID
:
memo
.
CreatorID
,
CreatedTs
:
time
.
Now
()
.
Unix
(),
Memo
:
&
webhook
.
Memo
{
ID
:
memo
.
ID
,
CreatorID
:
memo
.
CreatorID
,
CreatedTs
:
memo
.
CreatedTs
,
UpdatedTs
:
memo
.
UpdatedTs
,
Content
:
memo
.
Content
,
Visibility
:
memo
.
Visibility
.
String
(),
Pinned
:
memo
.
Pinned
,
ResourceList
:
func
()
[]
*
webhook
.
Resource
{
resources
:=
[]
*
webhook
.
Resource
{}
for
_
,
resource
:=
range
memo
.
ResourceList
{
resources
=
append
(
resources
,
&
webhook
.
Resource
{
ID
:
resource
.
ID
,
CreatorID
:
resource
.
CreatorID
,
CreatedTs
:
resource
.
CreatedTs
,
UpdatedTs
:
resource
.
UpdatedTs
,
Filename
:
resource
.
Filename
,
InternalPath
:
resource
.
InternalPath
,
ExternalLink
:
resource
.
ExternalLink
,
Type
:
resource
.
Type
,
Size
:
resource
.
Size
,
})
}
return
resources
}(),
RelationList
:
func
()
[]
*
webhook
.
MemoRelation
{
relations
:=
[]
*
webhook
.
MemoRelation
{}
for
_
,
relation
:=
range
memo
.
RelationList
{
relations
=
append
(
relations
,
&
webhook
.
MemoRelation
{
MemoID
:
relation
.
MemoID
,
RelatedMemoID
:
relation
.
RelatedMemoID
,
Type
:
relation
.
Type
.
String
(),
})
}
return
relations
}(),
},
}
}
api/v1/memo_relation.go
View file @
bc965f6a
...
...
@@ -18,6 +18,10 @@ const (
MemoRelationComment
MemoRelationType
=
"COMMENT"
)
func
(
t
MemoRelationType
)
String
()
string
{
return
string
(
t
)
}
type
MemoRelation
struct
{
MemoID
int32
`json:"memoId"`
RelatedMemoID
int32
`json:"relatedMemoId"`
...
...
plugin/webhook/webhook.go
0 → 100644
View file @
bc965f6a
package
webhook
import
(
"bytes"
"encoding/json"
"io"
"net/http"
"time"
"github.com/pkg/errors"
)
var
(
// timeout is the timeout for webhook request. Default to 30 seconds.
timeout
=
30
*
time
.
Second
)
type
Memo
struct
{
ID
int32
`json:"id"`
CreatorID
int32
`json:"creatorId"`
CreatedTs
int64
`json:"createdTs"`
UpdatedTs
int64
`json:"updatedTs"`
// Domain specific fields
Content
string
`json:"content"`
Visibility
string
`json:"visibility"`
Pinned
bool
`json:"pinned"`
ResourceList
[]
*
Resource
`json:"resourceList"`
RelationList
[]
*
MemoRelation
`json:"relationList"`
}
type
Resource
struct
{
ID
int32
`json:"id"`
// Standard fields
CreatorID
int32
`json:"creatorId"`
CreatedTs
int64
`json:"createdTs"`
UpdatedTs
int64
`json:"updatedTs"`
// Domain specific fields
Filename
string
`json:"filename"`
InternalPath
string
`json:"internalPath"`
ExternalLink
string
`json:"externalLink"`
Type
string
`json:"type"`
Size
int64
`json:"size"`
}
type
MemoRelation
struct
{
MemoID
int32
`json:"memoId"`
RelatedMemoID
int32
`json:"relatedMemoId"`
Type
string
`json:"type"`
}
// WebhookPayload is the payload of webhook request.
// nolint
type
WebhookPayload
struct
{
URL
string
`json:"url"`
ActivityType
string
`json:"activityType"`
CreatorID
int32
`json:"creatorId"`
CreatedTs
int64
`json:"createdTs"`
Memo
*
Memo
`json:"memo"`
}
// WebhookResponse is the response of webhook request.
// nolint
type
WebhookResponse
struct
{
Code
int
`json:"code"`
Message
string
`json:"message"`
}
// Post posts the message to webhook endpoint.
func
Post
(
payload
WebhookPayload
)
error
{
body
,
err
:=
json
.
Marshal
(
&
payload
)
if
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"failed to marshal webhook request to %s"
,
payload
.
URL
)
}
req
,
err
:=
http
.
NewRequest
(
"POST"
,
payload
.
URL
,
bytes
.
NewBuffer
(
body
))
if
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"failed to construct webhook request to %s"
,
payload
.
URL
)
}
req
.
Header
.
Set
(
"Content-Type"
,
"application/json"
)
client
:=
&
http
.
Client
{
Timeout
:
timeout
,
}
resp
,
err
:=
client
.
Do
(
req
)
if
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"failed to post webhook to %s"
,
payload
.
URL
)
}
b
,
err
:=
io
.
ReadAll
(
resp
.
Body
)
if
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"failed to read webhook response from %s"
,
payload
.
URL
)
}
defer
resp
.
Body
.
Close
()
if
resp
.
StatusCode
!=
http
.
StatusOK
{
return
errors
.
Errorf
(
"failed to post webhook %s, status code: %d, response body: %s"
,
payload
.
URL
,
resp
.
StatusCode
,
b
)
}
response
:=
&
WebhookResponse
{}
if
err
:=
json
.
Unmarshal
(
b
,
response
);
err
!=
nil
{
return
errors
.
Wrapf
(
err
,
"failed to unmarshal webhook response from %s"
,
payload
.
URL
)
}
if
response
.
Code
!=
0
{
return
errors
.
Errorf
(
"receive error code sent by webhook server, code %d, msg: %s"
,
response
.
Code
,
response
.
Message
)
}
return
nil
}
plugin/webhook/webhook_test.go
0 → 100644
View file @
bc965f6a
package
webhook
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