Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Event Webhook Support with DocRootChanged Event #1156

Merged
merged 24 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/converter/from_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ func FromProject(pbProject *api.Project) *types.Project {
Name: pbProject.Name,
AuthWebhookURL: pbProject.AuthWebhookUrl,
AuthWebhookMethods: pbProject.AuthWebhookMethods,
EventWebhookURL: pbProject.EventWebhookUrl,
EventWebhookEvents: pbProject.EventWebhookEvents,
ClientDeactivateThreshold: pbProject.ClientDeactivateThreshold,
PublicKey: pbProject.PublicKey,
SecretKey: pbProject.SecretKey,
Expand Down Expand Up @@ -922,6 +924,12 @@ func FromUpdatableProjectFields(pbProjectFields *api.UpdatableProjectFields) (*t
if pbProjectFields.AuthWebhookMethods != nil {
updatableProjectFields.AuthWebhookMethods = &pbProjectFields.AuthWebhookMethods.Methods
}
if pbProjectFields.EventWebhookUrl != nil {
updatableProjectFields.EventWebhookURL = &pbProjectFields.EventWebhookUrl.Value
}
if pbProjectFields.EventWebhookEvents != nil {
updatableProjectFields.EventWebhookEvents = &pbProjectFields.EventWebhookEvents.Events
}
if pbProjectFields.ClientDeactivateThreshold != nil {
updatableProjectFields.ClientDeactivateThreshold = &pbProjectFields.ClientDeactivateThreshold.Value
}
Expand Down
12 changes: 12 additions & 0 deletions api/converter/to_pb.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ func ToProject(project *types.Project) *api.Project {
Name: project.Name,
AuthWebhookUrl: project.AuthWebhookURL,
AuthWebhookMethods: project.AuthWebhookMethods,
EventWebhookUrl: project.EventWebhookURL,
EventWebhookEvents: project.EventWebhookEvents,
ClientDeactivateThreshold: project.ClientDeactivateThreshold,
PublicKey: project.PublicKey,
SecretKey: project.SecretKey,
Expand Down Expand Up @@ -562,6 +564,16 @@ func ToUpdatableProjectFields(fields *types.UpdatableProjectFields) (*api.Updata
} else {
pbUpdatableProjectFields.AuthWebhookMethods = nil
}
if fields.EventWebhookURL != nil {
pbUpdatableProjectFields.EventWebhookUrl = &wrapperspb.StringValue{Value: *fields.EventWebhookURL}
}
if fields.EventWebhookEvents != nil {
pbUpdatableProjectFields.EventWebhookEvents = &api.UpdatableProjectFields_EventWebhookEvents{
Events: *fields.EventWebhookEvents,
}
} else {
pbUpdatableProjectFields.EventWebhookEvents = nil
}
if fields.ClientDeactivateThreshold != nil {
pbUpdatableProjectFields.ClientDeactivateThreshold = &wrapperspb.StringValue{
Value: *fields.ClientDeactivateThreshold,
Expand Down
37 changes: 37 additions & 0 deletions api/docs/yorkie/v1/admin.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,18 @@ components:
description: ""
title: created_at
type: object
eventWebhookEvents:
additionalProperties: false
description: ""
items:
type: string
title: event_webhook_events
type: array
eventWebhookUrl:
additionalProperties: false
description: ""
title: event_webhook_url
type: string
id:
additionalProperties: false
description: ""
Expand Down Expand Up @@ -2132,6 +2144,18 @@ components:
description: ""
title: client_deactivate_threshold
type: object
eventWebhookEvents:
$ref: '#/components/schemas/yorkie.v1.UpdatableProjectFields.EventWebhookEvents'
additionalProperties: false
description: ""
title: event_webhook_events
type: object
eventWebhookUrl:
$ref: '#/components/schemas/google.protobuf.StringValue'
additionalProperties: false
description: ""
title: event_webhook_url
type: object
name:
$ref: '#/components/schemas/google.protobuf.StringValue'
additionalProperties: false
Expand All @@ -2153,6 +2177,19 @@ components:
type: array
title: AuthWebhookMethods
type: object
yorkie.v1.UpdatableProjectFields.EventWebhookEvents:
additionalProperties: false
description: ""
properties:
events:
additionalProperties: false
description: ""
items:
type: string
title: events
type: array
title: EventWebhookEvents
type: object
yorkie.v1.UpdateProjectRequest:
additionalProperties: false
description: ""
Expand Down
12 changes: 12 additions & 0 deletions api/docs/yorkie/v1/cluster.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,18 @@ components:
description: ""
title: created_at
type: object
eventWebhookEvents:
additionalProperties: false
description: ""
items:
type: string
title: event_webhook_events
type: array
eventWebhookUrl:
additionalProperties: false
description: ""
title: event_webhook_url
type: string
id:
additionalProperties: false
description: ""
Expand Down
37 changes: 37 additions & 0 deletions api/docs/yorkie/v1/resources.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1335,6 +1335,18 @@ components:
description: ""
title: created_at
type: object
eventWebhookEvents:
additionalProperties: false
description: ""
items:
type: string
title: event_webhook_events
type: array
eventWebhookUrl:
additionalProperties: false
description: ""
title: event_webhook_url
type: string
id:
additionalProperties: false
description: ""
Expand Down Expand Up @@ -1687,6 +1699,18 @@ components:
description: ""
title: client_deactivate_threshold
type: object
eventWebhookEvents:
$ref: '#/components/schemas/yorkie.v1.UpdatableProjectFields.EventWebhookEvents'
additionalProperties: false
description: ""
title: event_webhook_events
type: object
eventWebhookUrl:
$ref: '#/components/schemas/google.protobuf.StringValue'
additionalProperties: false
description: ""
title: event_webhook_url
type: object
name:
$ref: '#/components/schemas/google.protobuf.StringValue'
additionalProperties: false
Expand All @@ -1708,6 +1732,19 @@ components:
type: array
title: AuthWebhookMethods
type: object
yorkie.v1.UpdatableProjectFields.EventWebhookEvents:
additionalProperties: false
description: ""
properties:
events:
additionalProperties: false
description: ""
items:
type: string
title: events
type: array
title: EventWebhookEvents
type: object
yorkie.v1.User:
additionalProperties: false
description: ""
Expand Down
42 changes: 42 additions & 0 deletions api/types/event_webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2025 The Yorkie Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package types

// EventWebhookType represents event webhook type
type EventWebhookType string

const (
// DocRootChanged is an event that indicates the document's content was modified.
DocRootChanged EventWebhookType = "DocumentRootChanged"
)

// IsValidEventType checks whether the given event type is valid.
func IsValidEventType(eventType string) bool {
return eventType == string(DocRootChanged)
}

// EventWebhookAttribute represents the attribute of the webhook.
type EventWebhookAttribute struct {
Key string `json:"key"`
IssuedAt string `json:"issuedAt"`
}

// EventWebhookRequest represents the request of the webhook.
type EventWebhookRequest struct {
Type EventWebhookType `json:"type"`
Attributes EventWebhookAttribute `json:"attributes"`
}
14 changes: 14 additions & 0 deletions api/types/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const (
// modified by a change.
DocChangedEvent DocEventType = "document-changed"

// DocRootChangedEvent is an event indicating that document's root content
// is being changed by operation.
DocRootChangedEvent DocEventType = "document-root-changed"

// DocWatchedEvent is an event that occurs when document is watched
// by other clients.
DocWatchedEvent DocEventType = "document-watched"
Expand All @@ -43,6 +47,16 @@ const (
DocBroadcastEvent DocEventType = "document-broadcast"
)

// WebhookType returns a matched event webhook type.
func (t DocEventType) WebhookType() types.EventWebhookType {
switch t {
case DocRootChangedEvent:
return types.DocRootChanged
default:
return ""
}
}

// DocEventBody includes additional data specific to the DocEvent.
type DocEventBody struct {
Topic string
Expand Down
25 changes: 25 additions & 0 deletions api/types/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ type Project struct {
// AuthWebhookMethods is the methods that run the authorization webhook.
AuthWebhookMethods []string `json:"auth_webhook_methods"`

// EventWebhookURL is the url of the event webhook.
EventWebhookURL string `json:"event_webhook_url"`

// EventWebhookEvents are the events that event webhook will be triggered.
EventWebhookEvents []string `json:"event_webhook_events"`

// ClientDeactivateThreshold is the time after which clients in
// specific project are considered deactivate for housekeeping.
ClientDeactivateThreshold string `bson:"client_deactivate_threshold"`
Expand Down Expand Up @@ -73,3 +79,22 @@ func (p *Project) RequireAuth(method Method) bool {

return false
}

// RequireEventWebhook returns whether the given type requires to send event webhook.
func (p *Project) RequireEventWebhook(eventType EventWebhookType) bool {
if len(p.EventWebhookURL) == 0 {
return false
}

if len(p.EventWebhookEvents) == 0 {
return false
}

for _, t := range p.EventWebhookEvents {
if EventWebhookType(t) == eventType {
return true
}
}

return false
}
23 changes: 23 additions & 0 deletions api/types/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,27 @@ func TestProjectInfo(t *testing.T) {
}
assert.False(t, info3.RequireAuth(types.ActivateClient))
})

t.Run("require event webhook test", func(t *testing.T) {
// 1. Specify which event types to allow
validWebhookURL := "ValidWebhookURL"
info := &types.Project{
EventWebhookURL: validWebhookURL,
EventWebhookEvents: []string{string(types.DocRootChanged)},
}
assert.True(t, info.RequireEventWebhook(types.DocRootChanged))

// 2. No event types specified
info2 := &types.Project{
EventWebhookURL: validWebhookURL,
EventWebhookEvents: []string{},
}
assert.False(t, info2.RequireEventWebhook(types.DocRootChanged))

// 3. Empty webhook URL
info3 := &types.Project{
EventWebhookURL: "",
}
assert.False(t, info3.RequireEventWebhook(types.DocRootChanged))
})
}
35 changes: 34 additions & 1 deletion api/types/updatable_project_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,24 @@ type UpdatableProjectFields struct {
// AuthWebhookMethods is the methods that run the authorization webhook.
AuthWebhookMethods *[]string `bson:"auth_webhook_methods,omitempty" validate:"omitempty,invalid_webhook_method"`

// EventWebhookURL is the URL of the event webhook.
EventWebhookURL *string `bson:"event_webhook_url,omitempty" validate:"omitempty,url|emptystring"`

// EventWebhookEvents is the events that trigger the webhook.
EventWebhookEvents *[]string `bson:"event_webhook_events,omitempty" validate:"omitempty,invalid_webhook_event"`

// ClientDeactivateThreshold is the time after which clients in specific project are considered deactivate.
ClientDeactivateThreshold *string `bson:"client_deactivate_threshold,omitempty" validate:"omitempty,min=2,duration"`
}

// Validate validates the UpdatableProjectFields.
func (i *UpdatableProjectFields) Validate() error {
if i.Name == nil && i.AuthWebhookURL == nil && i.AuthWebhookMethods == nil && i.ClientDeactivateThreshold == nil {
if i.Name == nil &&
i.AuthWebhookURL == nil &&
i.AuthWebhookMethods == nil &&
i.ClientDeactivateThreshold == nil &&
i.EventWebhookURL == nil &&
i.EventWebhookEvents == nil {
return ErrEmptyProjectFields
}

Expand All @@ -68,8 +79,30 @@ func init() {
fmt.Fprintln(os.Stderr, "updatable project fields: ", err)
os.Exit(1)
}

if err := validation.RegisterTranslation("invalid_webhook_method", "given {0} is invalid method"); err != nil {
fmt.Fprintln(os.Stderr, "updatable project fields: ", err)
os.Exit(1)
}

if err := validation.RegisterValidation(
"invalid_webhook_event",
func(level validation.FieldLevel) bool {
eventTypes := level.Field().Interface().([]string)
for _, eventType := range eventTypes {
if !IsValidEventType(eventType) {
return false
}
}
return true
},
); err != nil {
fmt.Fprintln(os.Stderr, "updatable project fields: ", err)
os.Exit(1)
}

if err := validation.RegisterTranslation("invalid_webhook_event", "given {0} is invalid event type"); err != nil {
fmt.Fprintln(os.Stderr, "updatable project fields: ", err)
os.Exit(1)
}
}
Loading
Loading