fix: allow multiple hooks for same event

This commit is contained in:
Lakhan Samani 2023-03-26 07:20:45 +05:30
parent f324976801
commit deaf1e2ff7
15 changed files with 385 additions and 273 deletions

View File

@ -10,11 +10,15 @@ import (
// Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation // Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
// Event name has been kept unique as per initial design. But later on decided that we can have
// multiple hooks for same event so will be in a pattern `event_name-TIMESTAMP`
// Webhook model for db // Webhook model for db
type Webhook struct { type Webhook struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"` EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
Title string `json:"title" bson:"title" cql:"title" dynamo:"title"`
EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"` EndPoint string `json:"endpoint" bson:"endpoint" cql:"endpoint" dynamo:"endpoint"`
Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"` Headers string `json:"headers" bson:"headers" cql:"headers" dynamo:"headers"`
Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"` Enabled bool `json:"enabled" bson:"enabled" cql:"enabled" dynamo:"enabled"`
@ -26,14 +30,22 @@ type Webhook struct {
func (w *Webhook) AsAPIWebhook() *model.Webhook { func (w *Webhook) AsAPIWebhook() *model.Webhook {
headersMap := make(map[string]interface{}) headersMap := make(map[string]interface{})
json.Unmarshal([]byte(w.Headers), &headersMap) json.Unmarshal([]byte(w.Headers), &headersMap)
id := w.ID id := w.ID
if strings.Contains(id, Collections.Webhook+"/") { if strings.Contains(id, Collections.Webhook+"/") {
id = strings.TrimPrefix(id, Collections.Webhook+"/") id = strings.TrimPrefix(id, Collections.Webhook+"/")
} }
// If event name contains timestamp trim that part
if strings.Contains(w.EventName, "-") {
splitData := strings.Split(w.EventName, "-")
w.EventName = splitData[0]
}
// set default title to event name without dot(.)
if w.Title == "" {
w.Title = strings.Join(strings.Split(w.EventName, "."), " ")
}
return &model.Webhook{ return &model.Webhook{
ID: id, ID: id,
Title: refs.NewStringRef(w.Title),
EventName: refs.NewStringRef(w.EventName), EventName: refs.NewStringRef(w.EventName),
Endpoint: refs.NewStringRef(w.EndPoint), Endpoint: refs.NewStringRef(w.EndPoint),
Headers: headersMap, Headers: headersMap,

View File

@ -3,8 +3,10 @@ package arangodb
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver" arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -17,8 +19,12 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
webhook.Key = webhook.ID webhook.Key = webhook.ID
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
if webhook.Title == "" {
webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook) webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
@ -32,12 +38,15 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook) webhookCollection, _ := p.db.Collection(ctx, models.Collections.Webhook)
meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook) meta, err := webhookCollection.UpdateDocument(ctx, webhook.Key, webhook)
if err != nil { if err != nil {
return nil, err return nil, err
} }
webhook.Key = meta.Key webhook.Key = meta.Key
webhook.ID = meta.ID.String() webhook.ID = meta.ID.String()
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
@ -55,10 +64,8 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
return nil, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
paginationClone := pagination paginationClone := pagination
paginationClone.Total = cursor.Statistics().FullCount() paginationClone.Total = cursor.Statistics().FullCount()
for { for {
var webhook models.Webhook var webhook models.Webhook
meta, err := cursor.ReadDocument(ctx, &webhook) meta, err := cursor.ReadDocument(ctx, &webhook)
@ -87,13 +94,11 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
bindVars := map[string]interface{}{ bindVars := map[string]interface{}{
"webhook_id": webhookID, "webhook_id": webhookID,
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
for { for {
if !cursor.HasMore() { if !cursor.HasMore() {
if webhook.Key == "" { if webhook.Key == "" {
@ -110,32 +115,28 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
var webhook models.Webhook query := fmt.Sprintf("FOR d in %s FILTER d.event_name LIKE @event_name RETURN d", models.Collections.Webhook)
query := fmt.Sprintf("FOR d in %s FILTER d.event_name == @event_name RETURN d", models.Collections.Webhook)
bindVars := map[string]interface{}{ bindVars := map[string]interface{}{
"event_name": eventName, "event_name": eventName + "%",
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cursor.Close() defer cursor.Close()
webhooks := []*model.Webhook{}
for { for {
if !cursor.HasMore() { var webhook models.Webhook
if webhook.Key == "" { if _, err := cursor.ReadDocument(ctx, &webhook); driver.IsNoMoreDocuments(err) {
return nil, fmt.Errorf("webhook not found") // We're done
}
break break
} } else if err != nil {
_, err := cursor.ReadDocument(ctx, &webhook)
if err != nil {
return nil, err return nil, err
} }
webhooks = append(webhooks, webhook.AsAPIWebhook())
} }
return webhook.AsAPIWebhook(), nil return webhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook
@ -145,17 +146,14 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
if err != nil { if err != nil {
return err return err
} }
query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog) query := fmt.Sprintf("FOR d IN %s FILTER d.webhook_id == @webhook_id REMOVE { _key: d._key } IN %s", models.Collections.WebhookLog, models.Collections.WebhookLog)
bindVars := map[string]interface{}{ bindVars := map[string]interface{}{
"webhook_id": webhook.ID, "webhook_id": webhook.ID,
} }
cursor, err := p.db.Query(ctx, query, bindVars) cursor, err := p.db.Query(ctx, query, bindVars)
if err != nil { if err != nil {
return err return err
} }
defer cursor.Close() defer cursor.Close()
return nil return nil
} }

View File

@ -19,29 +19,29 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
if webhook.ID == "" { if webhook.ID == "" {
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.Title == "" {
existingHook, _ := p.GetWebhookByEventName(ctx, webhook.EventName) webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
if existingHook != nil {
return nil, fmt.Errorf("Webhook with %s event_name already exists", webhook.EventName)
} }
// Add timestamp to make event name unique for legacy version
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
insertQuery := fmt.Sprintf("INSERT INTO %s (id, title, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.Title, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
err := p.db.Query(insertQuery).Exec() err := p.db.Query(insertQuery).Exec()
if err != nil { if err != nil {
return nil, err return nil, err
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
bytes, err := json.Marshal(webhook) bytes, err := json.Marshal(webhook)
if err != nil { if err != nil {
return nil, err return nil, err
@ -54,22 +54,18 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
if err != nil { if err != nil {
return nil, err return nil, err
} }
updateFields := "" updateFields := ""
for key, value := range webhookMap { for key, value := range webhookMap {
if key == "_id" { if key == "_id" {
continue continue
} }
if key == "_key" { if key == "_key" {
continue continue
} }
if value == nil { if value == nil {
updateFields += fmt.Sprintf("%s = null,", key) updateFields += fmt.Sprintf("%s = null,", key)
continue continue
} }
valueType := reflect.TypeOf(value) valueType := reflect.TypeOf(value)
if valueType.Name() == "string" { if valueType.Name() == "string" {
updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string)) updateFields += fmt.Sprintf("%s = '%s', ", key, value.(string))
@ -79,7 +75,6 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
} }
updateFields = strings.Trim(updateFields, " ") updateFields = strings.Trim(updateFields, " ")
updateFields = strings.TrimSuffix(updateFields, ",") updateFields = strings.TrimSuffix(updateFields, ",")
query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID) query := fmt.Sprintf("UPDATE %s SET %s WHERE id = '%s'", KeySpace+"."+models.Collections.Webhook, updateFields, webhook.ID)
err = p.db.Query(query).Exec() err = p.db.Query(query).Exec()
if err != nil { if err != nil {
@ -92,24 +87,21 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) { func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{} webhooks := []*model.Webhook{}
paginationClone := pagination paginationClone := pagination
totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.Webhook) totalCountQuery := fmt.Sprintf(`SELECT COUNT(*) FROM %s`, KeySpace+"."+models.Collections.Webhook)
err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total) err := p.db.Query(totalCountQuery).Consistency(gocql.One).Scan(&paginationClone.Total)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// there is no offset in cassandra // there is no offset in cassandra
// so we fetch till limit + offset // so we fetch till limit + offset
// and return the results from offset to limit // and return the results from offset to limit
query := fmt.Sprintf("SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.Webhook, pagination.Limit+pagination.Offset) query := fmt.Sprintf("SELECT id, title, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s LIMIT %d", KeySpace+"."+models.Collections.Webhook, pagination.Limit+pagination.Offset)
scanner := p.db.Query(query).Iter().Scanner() scanner := p.db.Query(query).Iter().Scanner()
counter := int64(0) counter := int64(0)
for scanner.Next() { for scanner.Next() {
if counter >= pagination.Offset { if counter >= pagination.Offset {
var webhook models.Webhook var webhook models.Webhook
err := scanner.Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt) err := scanner.Scan(&webhook.ID, &webhook.Title, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -127,8 +119,8 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
// GetWebhookByID to get webhook by id // GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) { func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook var webhook models.Webhook
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, webhookID) query := fmt.Sprintf(`SELECT id, title, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE id = '%s' LIMIT 1`, KeySpace+"."+models.Collections.Webhook, webhookID)
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt) err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.Title, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -136,14 +128,19 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
var webhook models.Webhook query := fmt.Sprintf(`SELECT id, title, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name LIKE '%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName+"%s")
query := fmt.Sprintf(`SELECT id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name = '%s' LIMIT 1 ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName) scanner := p.db.Query(query).Iter().Scanner()
err := p.db.Query(query).Consistency(gocql.One).Scan(&webhook.ID, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt) webhooks := []*model.Webhook{}
if err != nil { for scanner.Next() {
return nil, err var webhook models.Webhook
err := scanner.Scan(&webhook.ID, &webhook.Title, &webhook.EventName, &webhook.EndPoint, &webhook.Headers, &webhook.Enabled, &webhook.CreatedAt, &webhook.UpdatedAt)
if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
} }
return webhook.AsAPIWebhook(), nil return webhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook

View File

@ -19,11 +19,14 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
if webhook.ID == "" { if webhook.ID == "" {
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.Title == "" {
webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
insertOpt := gocb.InsertOptions{ insertOpt := gocb.InsertOptions{
Context: ctx, Context: ctx,
} }
@ -37,7 +40,10 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
bytes, err := json.Marshal(webhook) bytes, err := json.Marshal(webhook)
if err != nil { if err != nil {
return nil, err return nil, err
@ -50,17 +56,13 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
if err != nil { if err != nil {
return nil, err return nil, err
} }
updateFields, params := GetSetFields(webhookMap) updateFields, params := GetSetFields(webhookMap)
query := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE _id='%s'`, p.scopeName, models.Collections.Webhook, updateFields, webhook.ID) query := fmt.Sprintf(`UPDATE %s.%s SET %s WHERE _id='%s'`, p.scopeName, models.Collections.Webhook, updateFields, webhook.ID)
_, err = p.db.Query(query, &gocb.QueryOptions{ _, err = p.db.Query(query, &gocb.QueryOptions{
Context: ctx, Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params, NamedParameters: params,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -72,7 +74,6 @@ func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) { func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
webhooks := []*model.Webhook{} webhooks := []*model.Webhook{}
paginationClone := pagination paginationClone := pagination
params := make(map[string]interface{}, 1) params := make(map[string]interface{}, 1)
params["offset"] = paginationClone.Offset params["offset"] = paginationClone.Offset
params["limit"] = paginationClone.Limit params["limit"] = paginationClone.Limit
@ -81,7 +82,7 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
return nil, err return nil, err
} }
paginationClone.Total = total paginationClone.Total = total
query := fmt.Sprintf("SELECT _id, env, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.Webhook) query := fmt.Sprintf("SELECT _id, title, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s OFFSET $offset LIMIT $limit", p.scopeName, models.Collections.Webhook)
queryResult, err := p.db.Query(query, &gocb.QueryOptions{ queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx, Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
@ -89,6 +90,8 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} else if err := queryResult.Err(); err != nil {
return nil, err
} }
for queryResult.Next() { for queryResult.Next() {
var webhook models.Webhook var webhook models.Webhook
@ -98,9 +101,6 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
} }
webhooks = append(webhooks, webhook.AsAPIWebhook()) webhooks = append(webhooks, webhook.AsAPIWebhook())
} }
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.Webhooks{ return &model.Webhooks{
Pagination: &paginationClone, Pagination: &paginationClone,
Webhooks: webhooks, Webhooks: webhooks,
@ -110,11 +110,9 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
// GetWebhookByID to get webhook by id // GetWebhookByID to get webhook by id
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) { func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
var webhook models.Webhook var webhook models.Webhook
params := make(map[string]interface{}, 1) params := make(map[string]interface{}, 1)
params["_id"] = webhookID params["_id"] = webhookID
query := fmt.Sprintf(`SELECT _id, title, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE _id=$_id LIMIT 1`, p.scopeName, models.Collections.Webhook)
query := fmt.Sprintf(`SELECT _id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE _id=$_id LIMIT 1`, p.scopeName, models.Collections.Webhook)
q, err := p.db.Query(query, &gocb.QueryOptions{ q, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx, Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
@ -124,37 +122,37 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
return nil, err return nil, err
} }
err = q.One(&webhook) err = q.One(&webhook)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
var webhook models.Webhook
params := make(map[string]interface{}, 1) params := make(map[string]interface{}, 1)
params["event_name"] = eventName params["event_name"] = eventName + "%"
query := fmt.Sprintf(`SELECT _id, title, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name LIKE $event_name`, p.scopeName, models.Collections.Webhook)
query := fmt.Sprintf(`SELECT _id, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name=$event_name LIMIT 1`, p.scopeName, models.Collections.Webhook) queryResult, err := p.db.Query(query, &gocb.QueryOptions{
q, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx, Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
NamedParameters: params, NamedParameters: params,
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} } else if err := queryResult.Err(); err != nil {
err = q.One(&webhook)
if err != nil {
return nil, err return nil, err
} }
webhooks := []*model.Webhook{}
return webhook.AsAPIWebhook(), nil for queryResult.Next() {
var webhook models.Webhook
err := queryResult.Row(&webhook)
if err != nil {
log.Fatal(err)
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
return webhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook

View File

@ -3,28 +3,32 @@ package dynamodb
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"strings"
"time" "time"
"github.com/google/uuid"
"github.com/guregu/dynamo"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/google/uuid"
"github.com/guregu/dynamo"
) )
// AddWebhook to add webhook // AddWebhook to add webhook
func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
collection := p.db.Table(models.Collections.Webhook) collection := p.db.Table(models.Collections.Webhook)
if webhook.ID == "" { if webhook.ID == "" {
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.Title == "" {
webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
err := collection.Put(webhook).RunWithContext(ctx) err := collection.Put(webhook).RunWithContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -33,11 +37,13 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
collection := p.db.Table(models.Collections.Webhook)
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
collection := p.db.Table(models.Collections.Webhook)
err := UpdateByHashKey(collection, "id", webhook.ID, webhook) err := UpdateByHashKey(collection, "id", webhook.ID, webhook)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -51,16 +57,13 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
var lastEval dynamo.PagingKey var lastEval dynamo.PagingKey
var iter dynamo.PagingIter var iter dynamo.PagingIter
var iteration int64 = 0 var iteration int64 = 0
collection := p.db.Table(models.Collections.Webhook) collection := p.db.Table(models.Collections.Webhook)
paginationClone := pagination paginationClone := pagination
scanner := collection.Scan() scanner := collection.Scan()
count, err := scanner.Count() count, err := scanner.Count()
if err != nil { if err != nil {
return nil, err return nil, err
} }
for (paginationClone.Offset + paginationClone.Limit) > iteration { for (paginationClone.Offset + paginationClone.Limit) > iteration {
iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter() iter = scanner.StartFrom(lastEval).Limit(paginationClone.Limit).Iter()
for iter.NextWithContext(ctx, &webhook) { for iter.NextWithContext(ctx, &webhook) {
@ -75,9 +78,7 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
lastEval = iter.LastEvaluatedKey() lastEval = iter.LastEvaluatedKey()
iteration += paginationClone.Limit iteration += paginationClone.Limit
} }
paginationClone.Total = count paginationClone.Total = count
return &model.Webhooks{ return &model.Webhooks{
Pagination: &paginationClone, Pagination: &paginationClone,
Webhooks: webhooks, Webhooks: webhooks,
@ -88,37 +89,29 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) { func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
collection := p.db.Table(models.Collections.Webhook) collection := p.db.Table(models.Collections.Webhook)
var webhook models.Webhook var webhook models.Webhook
err := collection.Get("id", webhookID).OneWithContext(ctx, &webhook) err := collection.Get("id", webhookID).OneWithContext(ctx, &webhook)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if webhook.ID == "" { if webhook.ID == "" {
return webhook.AsAPIWebhook(), errors.New("no documets found") return webhook.AsAPIWebhook(), errors.New("no documets found")
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
var webhook models.Webhook webhooks := []models.Webhook{}
collection := p.db.Table(models.Collections.Webhook) collection := p.db.Table(models.Collections.Webhook)
err := collection.Scan().Index("event_name").Filter("contains(event_name, ?)", eventName).AllWithContext(ctx, &webhooks)
iter := collection.Scan().Index("event_name").Filter("'event_name' = ?", eventName).Iter()
for iter.NextWithContext(ctx, &webhook) {
return webhook.AsAPIWebhook(), nil
}
err := iter.Err()
if err != nil { if err != nil {
return webhook.AsAPIWebhook(), err return nil, err
} }
return webhook.AsAPIWebhook(), nil resWebhooks := []*model.Webhook{}
for _, w := range webhooks {
resWebhooks = append(resWebhooks, w.AsAPIWebhook())
}
return resWebhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook
@ -133,7 +126,6 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
return err return err
} }
webhookLogs, errIs := p.ListWebhookLogs(ctx, pagination, webhook.ID) webhookLogs, errIs := p.ListWebhookLogs(ctx, pagination, webhook.ID)
for _, webhookLog := range webhookLogs.WebhookLogs { for _, webhookLog := range webhookLogs.WebhookLogs {
err = webhookLogCollection.Delete("id", webhookLog.ID).RunWithContext(ctx) err = webhookLogCollection.Delete("id", webhookLog.ID).RunWithContext(ctx)
if err != nil { if err != nil {

View File

@ -2,6 +2,8 @@ package mongodb
import ( import (
"context" "context"
"fmt"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@ -16,11 +18,14 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
if webhook.ID == "" { if webhook.ID == "" {
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.Title == "" {
webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection()) webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
_, err := webhookCollection.InsertOne(ctx, webhook) _, err := webhookCollection.InsertOne(ctx, webhook)
if err != nil { if err != nil {
@ -32,39 +37,37 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection()) webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
_, err := webhookCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": webhook.ID}}, bson.M{"$set": webhook}, options.MergeUpdateOptions()) _, err := webhookCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": webhook.ID}}, bson.M{"$set": webhook}, options.MergeUpdateOptions())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
// ListWebhooks to list webhook // ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) { func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
var webhooks []*model.Webhook webhooks := []*model.Webhook{}
opts := options.Find() opts := options.Find()
opts.SetLimit(pagination.Limit) opts.SetLimit(pagination.Limit)
opts.SetSkip(pagination.Offset) opts.SetSkip(pagination.Offset)
opts.SetSort(bson.M{"created_at": -1}) opts.SetSort(bson.M{"created_at": -1})
paginationClone := pagination paginationClone := pagination
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection()) webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
count, err := webhookCollection.CountDocuments(ctx, bson.M{}, options.Count()) count, err := webhookCollection.CountDocuments(ctx, bson.M{}, options.Count())
if err != nil { if err != nil {
return nil, err return nil, err
} }
paginationClone.Total = count paginationClone.Total = count
cursor, err := webhookCollection.Find(ctx, bson.M{}, opts) cursor, err := webhookCollection.Find(ctx, bson.M{}, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer cursor.Close(ctx) defer cursor.Close(ctx)
for cursor.Next(ctx) { for cursor.Next(ctx) {
var webhook models.Webhook var webhook models.Webhook
err := cursor.Decode(&webhook) err := cursor.Decode(&webhook)
@ -73,7 +76,6 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
} }
webhooks = append(webhooks, webhook.AsAPIWebhook()) webhooks = append(webhooks, webhook.AsAPIWebhook())
} }
return &model.Webhooks{ return &model.Webhooks{
Pagination: &paginationClone, Pagination: &paginationClone,
Webhooks: webhooks, Webhooks: webhooks,
@ -92,14 +94,27 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
var webhook models.Webhook webhooks := []*model.Webhook{}
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection()) webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
err := webhookCollection.FindOne(ctx, bson.M{"event_name": eventName}).Decode(&webhook) opts := options.Find()
opts.SetSort(bson.M{"created_at": -1})
cursor, err := webhookCollection.Find(ctx, bson.M{
"event_name": fmt.Sprintf("'^%s'", eventName),
}, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return webhook.AsAPIWebhook(), nil defer cursor.Close(ctx)
for cursor.Next(ctx) {
var webhook models.Webhook
err := cursor.Decode(&webhook)
if err != nil {
return nil, err
}
webhooks = append(webhooks, webhook.AsAPIWebhook())
}
return webhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook
@ -109,12 +124,10 @@ func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) er
if err != nil { if err != nil {
return err return err
} }
webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection()) webhookLogCollection := p.db.Collection(models.Collections.WebhookLog, options.Collection())
_, err = webhookLogCollection.DeleteMany(nil, bson.M{"webhook_id": webhook.ID}, options.Delete()) _, err = webhookLogCollection.DeleteMany(nil, bson.M{"webhook_id": webhook.ID}, options.Delete())
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }

View File

@ -2,6 +2,8 @@ package provider_template
import ( import (
"context" "context"
"fmt"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@ -14,16 +16,24 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
if webhook.ID == "" { if webhook.ID == "" {
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.Title == "" {
webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
@ -38,7 +48,7 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
return nil, nil return nil, nil
} }

View File

@ -56,7 +56,7 @@ type Provider interface {
// GetWebhookByID to get webhook by id // GetWebhookByID to get webhook by id
GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error)
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error)
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook
DeleteWebhook(ctx context.Context, webhook *model.Webhook) error DeleteWebhook(ctx context.Context, webhook *model.Webhook) error

View File

@ -2,6 +2,8 @@ package sql
import ( import (
"context" "context"
"fmt"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
@ -14,10 +16,14 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
if webhook.ID == "" { if webhook.ID == "" {
webhook.ID = uuid.New().String() webhook.ID = uuid.New().String()
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.Title == "" {
webhook.Title = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
res := p.db.Create(&webhook) res := p.db.Create(&webhook)
if res.Error != nil { if res.Error != nil {
return nil, res.Error return nil, res.Error
@ -28,33 +34,31 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
// UpdateWebhook to update webhook // UpdateWebhook to update webhook
func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) { func (p *provider) UpdateWebhook(ctx context.Context, webhook models.Webhook) (*model.Webhook, error) {
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
// Event is changed
if !strings.Contains(webhook.EventName, "-") {
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
}
result := p.db.Save(&webhook) result := p.db.Save(&webhook)
if result.Error != nil { if result.Error != nil {
return nil, result.Error return nil, result.Error
} }
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil
} }
// ListWebhooks to list webhook // ListWebhooks to list webhook
func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) { func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination) (*model.Webhooks, error) {
var webhooks []models.Webhook var webhooks []models.Webhook
result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhooks) result := p.db.Limit(int(pagination.Limit)).Offset(int(pagination.Offset)).Order("created_at DESC").Find(&webhooks)
if result.Error != nil { if result.Error != nil {
return nil, result.Error return nil, result.Error
} }
var total int64 var total int64
totalRes := p.db.Model(&models.Webhook{}).Count(&total) totalRes := p.db.Model(&models.Webhook{}).Count(&total)
if totalRes.Error != nil { if totalRes.Error != nil {
return nil, totalRes.Error return nil, totalRes.Error
} }
paginationClone := pagination paginationClone := pagination
paginationClone.Total = total paginationClone.Total = total
responseWebhooks := []*model.Webhook{} responseWebhooks := []*model.Webhook{}
for _, w := range webhooks { for _, w := range webhooks {
responseWebhooks = append(responseWebhooks, w.AsAPIWebhook()) responseWebhooks = append(responseWebhooks, w.AsAPIWebhook())
@ -77,14 +81,17 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
} }
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) (*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
var webhook models.Webhook var webhooks []models.Webhook
result := p.db.Where("event_name LIKE ?", eventName+"%").Find(&webhooks)
result := p.db.Where("event_name = ?", eventName).First(&webhook)
if result.Error != nil { if result.Error != nil {
return nil, result.Error return nil, result.Error
} }
return webhook.AsAPIWebhook(), nil responseWebhooks := []*model.Webhook{}
for _, w := range webhooks {
responseWebhooks = append(responseWebhooks, w.AsAPIWebhook())
}
return responseWebhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook

View File

@ -281,6 +281,7 @@ type ComplexityRoot struct {
EventName func(childComplexity int) int EventName func(childComplexity int) int
Headers func(childComplexity int) int Headers func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Title func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
} }
@ -1854,6 +1855,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Webhook.ID(childComplexity), true return e.complexity.Webhook.ID(childComplexity), true
case "Webhook.title":
if e.complexity.Webhook.Title == nil {
break
}
return e.complexity.Webhook.Title(childComplexity), true
case "Webhook.updated_at": case "Webhook.updated_at":
if e.complexity.Webhook.UpdatedAt == nil { if e.complexity.Webhook.UpdatedAt == nil {
break break
@ -2210,6 +2218,7 @@ type GenerateJWTKeysResponse {
type Webhook { type Webhook {
id: ID! id: ID!
title: String
event_name: String event_name: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean
@ -2500,6 +2509,7 @@ input ListWebhookLogRequest {
} }
input AddWebhookRequest { input AddWebhookRequest {
title: String!
event_name: String! event_name: String!
endpoint: String! endpoint: String!
enabled: Boolean! enabled: Boolean!
@ -2508,6 +2518,7 @@ input AddWebhookRequest {
input UpdateWebhookRequest { input UpdateWebhookRequest {
id: ID! id: ID!
title: String
event_name: String event_name: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean
@ -10141,6 +10152,8 @@ func (ec *executionContext) fieldContext_Query__webhook(ctx context.Context, fie
switch field.Name { switch field.Name {
case "id": case "id":
return ec.fieldContext_Webhook_id(ctx, field) return ec.fieldContext_Webhook_id(ctx, field)
case "title":
return ec.fieldContext_Webhook_title(ctx, field)
case "event_name": case "event_name":
return ec.fieldContext_Webhook_event_name(ctx, field) return ec.fieldContext_Webhook_event_name(ctx, field)
case "endpoint": case "endpoint":
@ -12160,6 +12173,47 @@ func (ec *executionContext) fieldContext_Webhook_id(ctx context.Context, field g
return fc, nil return fc, nil
} }
func (ec *executionContext) _Webhook_title(ctx context.Context, field graphql.CollectedField, obj *model.Webhook) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Webhook_title(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Title, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Webhook_title(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Webhook",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type String does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Webhook_event_name(ctx context.Context, field graphql.CollectedField, obj *model.Webhook) (ret graphql.Marshaler) { func (ec *executionContext) _Webhook_event_name(ctx context.Context, field graphql.CollectedField, obj *model.Webhook) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Webhook_event_name(ctx, field) fc, err := ec.fieldContext_Webhook_event_name(ctx, field)
if err != nil { if err != nil {
@ -12905,6 +12959,8 @@ func (ec *executionContext) fieldContext_Webhooks_webhooks(ctx context.Context,
switch field.Name { switch field.Name {
case "id": case "id":
return ec.fieldContext_Webhook_id(ctx, field) return ec.fieldContext_Webhook_id(ctx, field)
case "title":
return ec.fieldContext_Webhook_title(ctx, field)
case "event_name": case "event_name":
return ec.fieldContext_Webhook_event_name(ctx, field) return ec.fieldContext_Webhook_event_name(ctx, field)
case "endpoint": case "endpoint":
@ -14756,13 +14812,21 @@ func (ec *executionContext) unmarshalInputAddWebhookRequest(ctx context.Context,
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"event_name", "endpoint", "enabled", "headers"} fieldsInOrder := [...]string{"title", "event_name", "endpoint", "enabled", "headers"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
continue continue
} }
switch k { switch k {
case "title":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("title"))
it.Title, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "event_name": case "event_name":
var err error var err error
@ -16612,7 +16676,7 @@ func (ec *executionContext) unmarshalInputUpdateWebhookRequest(ctx context.Conte
asMap[k] = v asMap[k] = v
} }
fieldsInOrder := [...]string{"id", "event_name", "endpoint", "enabled", "headers"} fieldsInOrder := [...]string{"id", "title", "event_name", "endpoint", "enabled", "headers"}
for _, k := range fieldsInOrder { for _, k := range fieldsInOrder {
v, ok := asMap[k] v, ok := asMap[k]
if !ok { if !ok {
@ -16627,6 +16691,14 @@ func (ec *executionContext) unmarshalInputUpdateWebhookRequest(ctx context.Conte
if err != nil { if err != nil {
return it, err return it, err
} }
case "title":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("title"))
it.Title, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "event_name": case "event_name":
var err error var err error
@ -18509,6 +18581,10 @@ func (ec *executionContext) _Webhook(ctx context.Context, sel ast.SelectionSet,
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "title":
out.Values[i] = ec._Webhook_title(ctx, field, obj)
case "event_name": case "event_name":
out.Values[i] = ec._Webhook_event_name(ctx, field, obj) out.Values[i] = ec._Webhook_event_name(ctx, field, obj)

View File

@ -10,6 +10,7 @@ type AddEmailTemplateRequest struct {
} }
type AddWebhookRequest struct { type AddWebhookRequest struct {
Title string `json:"title"`
EventName string `json:"event_name"` EventName string `json:"event_name"`
Endpoint string `json:"endpoint"` Endpoint string `json:"endpoint"`
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
@ -388,6 +389,7 @@ type UpdateUserInput struct {
type UpdateWebhookRequest struct { type UpdateWebhookRequest struct {
ID string `json:"id"` ID string `json:"id"`
Title *string `json:"title"`
EventName *string `json:"event_name"` EventName *string `json:"event_name"`
Endpoint *string `json:"endpoint"` Endpoint *string `json:"endpoint"`
Enabled *bool `json:"enabled"` Enabled *bool `json:"enabled"`
@ -462,6 +464,7 @@ type VerifyOTPRequest struct {
type Webhook struct { type Webhook struct {
ID string `json:"id"` ID string `json:"id"`
Title *string `json:"title"`
EventName *string `json:"event_name"` EventName *string `json:"event_name"`
Endpoint *string `json:"endpoint"` Endpoint *string `json:"endpoint"`
Enabled *bool `json:"enabled"` Enabled *bool `json:"enabled"`

View File

@ -168,6 +168,7 @@ type GenerateJWTKeysResponse {
type Webhook { type Webhook {
id: ID! id: ID!
title: String
event_name: String event_name: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean
@ -458,6 +459,7 @@ input ListWebhookLogRequest {
} }
input AddWebhookRequest { input AddWebhookRequest {
title: String!
event_name: String! event_name: String!
endpoint: String! endpoint: String!
enabled: Boolean! enabled: Boolean!
@ -466,6 +468,7 @@ input AddWebhookRequest {
input UpdateWebhookRequest { input UpdateWebhookRequest {
id: ID! id: ID!
title: String
event_name: String event_name: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean

View File

@ -24,45 +24,47 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
// get webhook // get webhook
webhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent) webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, webhook) assert.NotNil(t, webhooks)
// it should completely replace headers assert.Greater(t, len(webhooks), 0)
webhook.Headers = map[string]interface{}{ for _, webhook := range webhooks {
"x-new-test": "test", // it should completely replace headers
webhook.Headers = map[string]interface{}{
"x-new-test": "test",
}
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: webhook.ID,
Headers: webhook.Headers,
Enabled: refs.NewBoolRef(false),
Endpoint: refs.NewStringRef("https://sometest.com"),
})
assert.NoError(t, err)
assert.NotEmpty(t, res)
assert.NotEmpty(t, res.Message)
} }
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{ updatedWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
ID: webhook.ID,
Headers: webhook.Headers,
Enabled: refs.NewBoolRef(false),
Endpoint: refs.NewStringRef("https://sometest.com"),
})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, res) assert.NotNil(t, updatedWebhooks)
assert.NotEmpty(t, res.Message) for _, updatedWebhook := range updatedWebhooks {
assert.Contains(t, refs.StringValue(updatedWebhook.EventName), constants.UserDeletedWebhookEvent)
updatedWebhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent) assert.Len(t, updatedWebhook.Headers, 1)
assert.NoError(t, err) assert.False(t, refs.BoolValue(updatedWebhook.Enabled))
assert.NotNil(t, updatedWebhook) for key, val := range updatedWebhook.Headers {
assert.Equal(t, webhook.ID, updatedWebhook.ID) assert.Equal(t, "x-new-test", key)
assert.Equal(t, refs.StringValue(webhook.EventName), refs.StringValue(updatedWebhook.EventName)) assert.Equal(t, "test", val)
assert.Len(t, updatedWebhook.Headers, 1) }
assert.False(t, refs.BoolValue(updatedWebhook.Enabled)) assert.Equal(t, "https://sometest.com", refs.StringValue(updatedWebhook.Endpoint))
for key, val := range updatedWebhook.Headers { res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
assert.Equal(t, val, webhook.Headers[key]) ID: updatedWebhook.ID,
Headers: updatedWebhook.Headers,
Enabled: refs.NewBoolRef(true),
Endpoint: refs.NewStringRef(s.TestInfo.WebhookEndpoint),
})
assert.NoError(t, err)
assert.NotEmpty(t, res)
assert.NotEmpty(t, res.Message)
} }
assert.Equal(t, refs.StringValue(updatedWebhook.Endpoint), "https://sometest.com")
res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: webhook.ID,
Headers: webhook.Headers,
Enabled: refs.NewBoolRef(true),
Endpoint: refs.NewStringRef(s.TestInfo.WebhookEndpoint),
})
assert.NoError(t, err)
assert.NotEmpty(t, res)
assert.NotEmpty(t, res.Message)
}) })
} }

View File

@ -25,18 +25,20 @@ func webhookTest(t *testing.T, s TestSetup) {
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
// get webhook by event name // get webhook by event name
webhook, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent) webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, webhook) assert.NotNil(t, webhooks)
assert.Greater(t, len(webhooks), 0)
res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{ for _, webhook := range webhooks {
ID: webhook.ID, res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{
}) ID: webhook.ID,
assert.NoError(t, err) })
assert.Equal(t, res.ID, webhook.ID) assert.NoError(t, err)
assert.Equal(t, refs.StringValue(res.Endpoint), refs.StringValue(webhook.Endpoint)) assert.Equal(t, res.ID, webhook.ID)
assert.Equal(t, refs.StringValue(res.EventName), refs.StringValue(webhook.EventName)) assert.Equal(t, refs.StringValue(res.Endpoint), refs.StringValue(webhook.Endpoint))
assert.Equal(t, refs.BoolValue(res.Enabled), refs.BoolValue(webhook.Enabled)) assert.Equal(t, refs.StringValue(res.EventName), refs.StringValue(webhook.EventName))
assert.Len(t, res.Headers, len(webhook.Headers)) assert.Equal(t, refs.BoolValue(res.Enabled), refs.BoolValue(webhook.Enabled))
assert.Len(t, res.Headers, len(webhook.Headers))
}
}) })
} }

View File

@ -17,98 +17,97 @@ import (
) )
func RegisterEvent(ctx context.Context, eventName string, authRecipe string, user models.User) error { func RegisterEvent(ctx context.Context, eventName string, authRecipe string, user models.User) error {
webhook, err := db.Provider.GetWebhookByEventName(ctx, eventName) webhooks, err := db.Provider.GetWebhookByEventName(ctx, eventName)
if err != nil { if err != nil {
return err return err
} }
for _, webhook := range webhooks {
if !refs.BoolValue(webhook.Enabled) {
return nil
}
userBytes, err := json.Marshal(user.AsAPIUser())
if err != nil {
log.Debug("error marshalling user obj: ", err)
return err
}
userMap := map[string]interface{}{}
err = json.Unmarshal(userBytes, &userMap)
if err != nil {
log.Debug("error un-marshalling user obj: ", err)
return err
}
if !refs.BoolValue(webhook.Enabled) { reqBody := map[string]interface{}{
return nil "event_name": eventName,
} "user": userMap,
}
userBytes, err := json.Marshal(user.AsAPIUser()) if eventName == constants.UserLoginWebhookEvent || eventName == constants.UserSignUpWebhookEvent {
if err != nil { reqBody["auth_recipe"] = authRecipe
log.Debug("error marshalling user obj: ", err) }
return err
}
userMap := map[string]interface{}{}
err = json.Unmarshal(userBytes, &userMap)
if err != nil {
log.Debug("error un-marshalling user obj: ", err)
return err
}
reqBody := map[string]interface{}{ requestBody, err := json.Marshal(reqBody)
"event_name": eventName, if err != nil {
"user": userMap, log.Debug("error marshalling requestBody obj: ", err)
} return err
}
if eventName == constants.UserLoginWebhookEvent || eventName == constants.UserSignUpWebhookEvent { // dont trigger webhook call in case of test
reqBody["auth_recipe"] = authRecipe envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
} if err != nil {
return err
}
if envKey == constants.TestEnv {
db.Provider.AddWebhookLog(ctx, models.WebhookLog{
HttpStatus: 200,
Request: string(requestBody),
Response: string(`{"message": "test"}`),
WebhookID: webhook.ID,
})
requestBody, err := json.Marshal(reqBody) return nil
if err != nil { }
log.Debug("error marshalling requestBody obj: ", err)
return err
}
// dont trigger webhook call in case of test requestBytesBuffer := bytes.NewBuffer(requestBody)
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv) req, err := http.NewRequest("POST", refs.StringValue(webhook.Endpoint), requestBytesBuffer)
if err != nil { if err != nil {
return err log.Debug("error creating webhook post request: ", err)
} return err
if envKey == constants.TestEnv { }
db.Provider.AddWebhookLog(ctx, models.WebhookLog{ req.Header.Set("Content-Type", "application/json")
HttpStatus: 200,
if webhook.Headers != nil {
for key, val := range webhook.Headers {
req.Header.Set(key, val.(string))
}
}
client := &http.Client{Timeout: time.Second * 30}
resp, err := client.Do(req)
if err != nil {
log.Debug("error making request: ", err)
return err
}
defer resp.Body.Close()
responseBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Debug("error reading response: ", err)
return err
}
statusCode := int64(resp.StatusCode)
_, err = db.Provider.AddWebhookLog(ctx, models.WebhookLog{
HttpStatus: statusCode,
Request: string(requestBody), Request: string(requestBody),
Response: string(`{"message": "test"}`), Response: string(responseBytes),
WebhookID: webhook.ID, WebhookID: webhook.ID,
}) })
return nil if err != nil {
} log.Debug("failed to add webhook log: ", err)
return err
requestBytesBuffer := bytes.NewBuffer(requestBody)
req, err := http.NewRequest("POST", refs.StringValue(webhook.Endpoint), requestBytesBuffer)
if err != nil {
log.Debug("error creating webhook post request: ", err)
return err
}
req.Header.Set("Content-Type", "application/json")
if webhook.Headers != nil {
for key, val := range webhook.Headers {
req.Header.Set(key, val.(string))
} }
} }
client := &http.Client{Timeout: time.Second * 30}
resp, err := client.Do(req)
if err != nil {
log.Debug("error making request: ", err)
return err
}
defer resp.Body.Close()
responseBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Debug("error reading response: ", err)
return err
}
statusCode := int64(resp.StatusCode)
_, err = db.Provider.AddWebhookLog(ctx, models.WebhookLog{
HttpStatus: statusCode,
Request: string(requestBody),
Response: string(responseBytes),
WebhookID: webhook.ID,
})
if err != nil {
log.Debug("failed to add webhook log: ", err)
return err
}
return nil return nil
} }