Add edit webhook functionality

This commit is contained in:
Luis Eduardo Jeréz Girón
2024-09-01 21:41:45 -06:00
parent 718c7bc312
commit 0453aa946d
6 changed files with 207 additions and 9 deletions

View File

@@ -3,6 +3,7 @@ UPDATE webhooks
SET
name = COALESCE(sqlc.narg('name'), name),
is_active = COALESCE(sqlc.narg('is_active'), is_active),
event_type = COALESCE(sqlc.narg('event_type'), event_type),
target_ids = COALESCE(sqlc.narg('target_ids'), target_ids),
url = COALESCE(sqlc.narg('url'), url),
method = COALESCE(sqlc.narg('method'), method),

View File

@@ -137,23 +137,53 @@ func createAndUpdateWebhookForm(
))
}
pickedTargetIds := ""
if len(pickedWebhook.TargetIds) > 0 {
for _, tid := range pickedWebhook.TargetIds {
pickedTargetIds += fmt.Sprintf("'%s',", tid.String())
}
}
return html.Div(
html.Class("space-y-2"),
alpine.XData(`{
eventType: "",
targetIds: [],
eventType: "`+pickedWebhook.EventType+`",
targetIds: [`+pickedTargetIds+`],
isEventType(eventType) {
return this.eventType === eventType
},
init () {
autoGrowHeadersTextarea() {
textareaAutoGrow($refs.headersTextarea)
},
autoGrowBodyTextarea() {
textareaAutoGrow($refs.bodyTextarea)
},
formatHeadersTextarea() {
const el = $refs.headersTextarea
el.value = formatJson(el.value)
this.autoGrowHeadersTextarea()
},
formatBodyTextarea() {
const el = $refs.bodyTextarea
el.value = formatJson(el.value)
this.autoGrowBodyTextarea()
},
init() {
$watch('eventType', (value, oldValue) => {
if (value !== oldValue) {
this.targetIds = []
}
})
this.formatHeadersTextarea()
this.formatBodyTextarea()
}
}`),
@@ -296,6 +326,9 @@ func createAndUpdateWebhookForm(
Placeholder: `{ "Authorization": "Bearer my-token" }`,
HelpText: `By default it will send a { "Content-Type": "application/json" } header.`,
Children: []gomponents.Node{
alpine.XRef("headersTextarea"),
alpine.XOn("click.outside", "formatHeadersTextarea()"),
alpine.XOn("input", "autoGrowHeadersTextarea()"),
gomponents.If(
shouldPrefill, gomponents.Text(pickedWebhook.Headers.String),
),
@@ -308,6 +341,9 @@ func createAndUpdateWebhookForm(
Placeholder: `{ "key": "value" }`,
HelpText: `By default it will send an empty json object {}.`,
Children: []gomponents.Node{
alpine.XRef("bodyTextarea"),
alpine.XOn("click.outside", "formatBodyTextarea()"),
alpine.XOn("input", "autoGrowBodyTextarea()"),
gomponents.If(
shouldPrefill, gomponents.Text(pickedWebhook.Body.String),
),

View File

@@ -86,7 +86,7 @@ func createWebhookForm(
backups []dbgen.Backup,
) gomponents.Node {
return html.Form(
htmx.HxPost("/dashboard/webhooks"),
htmx.HxPost("/dashboard/webhooks/create"),
htmx.HxDisabledELT("find button[type='submit']"),
html.Class("space-y-2"),
@@ -111,7 +111,7 @@ func createWebhookButton() gomponents.Node {
Title: "Create webhook",
Content: []gomponents.Node{
html.Div(
htmx.HxGet("/dashboard/webhooks/create-form"),
htmx.HxGet("/dashboard/webhooks/create"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("intersect once"),
html.Class("p-10 flex justify-center"),

View File

@@ -0,0 +1,153 @@
package webhooks
import (
"database/sql"
"net/http"
lucide "github.com/eduardolat/gomponents-lucide"
"github.com/eduardolat/pgbackweb/internal/database/dbgen"
"github.com/eduardolat/pgbackweb/internal/util/echoutil"
"github.com/eduardolat/pgbackweb/internal/validate"
"github.com/eduardolat/pgbackweb/internal/view/web/component"
"github.com/eduardolat/pgbackweb/internal/view/web/htmx"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/html"
)
type editWebhookDTO struct {
Name string `form:"name" validate:"required"`
EventType string `form:"event_type" validate:"required"`
TargetIds []uuid.UUID `form:"target_ids" validate:"required,gt=0"`
IsActive string `form:"is_active" validate:"required,oneof=true false"`
Url string `form:"url" validate:"required,url"`
Method string `form:"method" validate:"required,oneof=GET POST"`
Headers string `form:"headers" validate:"omitempty,json"`
Body string `form:"body" validate:"omitempty,json"`
}
func (h *handlers) editWebhookHandler(c echo.Context) error {
ctx := c.Request().Context()
webhookID, err := uuid.Parse(c.Param("webhookID"))
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
var formData editWebhookDTO
if err := c.Bind(&formData); err != nil {
return htmx.RespondToastError(c, err.Error())
}
if err := validate.Struct(&formData); err != nil {
return htmx.RespondToastError(c, err.Error())
}
_, err = h.servs.WebhooksService.UpdateWebhook(
ctx, dbgen.WebhooksServiceUpdateWebhookParams{
WebhookID: webhookID,
Name: sql.NullString{String: formData.Name, Valid: true},
EventType: sql.NullString{String: formData.EventType, Valid: true},
TargetIds: formData.TargetIds,
IsActive: sql.NullBool{Bool: formData.IsActive == "true", Valid: true},
Url: sql.NullString{String: formData.Url, Valid: true},
Method: sql.NullString{String: formData.Method, Valid: true},
Headers: sql.NullString{String: formData.Headers, Valid: true},
Body: sql.NullString{String: formData.Body, Valid: true},
},
)
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
return htmx.RespondAlertWithRefresh(c, "Webhook updated")
}
func (h *handlers) editWebhookFormHandler(c echo.Context) error {
ctx := c.Request().Context()
webhookID, err := uuid.Parse(c.Param("webhookID"))
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
webhook, err := h.servs.WebhooksService.GetWebhook(ctx, webhookID)
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
databases, err := h.servs.DatabasesService.GetAllDatabases(ctx)
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
destinations, err := h.servs.DestinationsService.GetAllDestinations(ctx)
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
backups, err := h.servs.BackupsService.GetAllBackups(ctx)
if err != nil {
return htmx.RespondToastError(c, err.Error())
}
return echoutil.RenderGomponent(c, http.StatusOK, editWebhookForm(
webhook, databases, destinations, backups,
))
}
func editWebhookForm(
webhook dbgen.Webhook,
databases []dbgen.DatabasesServiceGetAllDatabasesRow,
destinations []dbgen.DestinationsServiceGetAllDestinationsRow,
backups []dbgen.Backup,
) gomponents.Node {
return html.Form(
htmx.HxPost("/dashboard/webhooks/"+webhook.ID.String()+"/edit"),
htmx.HxDisabledELT("find button[type='submit']"),
html.Class("space-y-2"),
createAndUpdateWebhookForm(databases, destinations, backups, webhook),
html.Div(
html.Class("flex justify-end items-center space-x-2 pt-2"),
component.HxLoadingMd(),
html.Button(
html.Class("btn btn-primary"),
html.Type("submit"),
component.SpanText("Save"),
lucide.Save(),
),
),
)
}
func editWebhookButton(webhookID uuid.UUID) gomponents.Node {
mo := component.Modal(component.ModalParams{
Size: component.SizeLg,
Title: "Edit webhook",
Content: []gomponents.Node{
html.Div(
htmx.HxGet("/dashboard/webhooks/"+webhookID.String()+"/edit"),
htmx.HxSwap("outerHTML"),
htmx.HxTrigger("intersect once"),
html.Class("p-10 flex justify-center"),
component.HxLoadingMd(),
),
},
})
button := html.Button(
mo.OpenerAttr,
html.Class("btn btn-neutral btn-sm btn-square btn-ghost"),
lucide.Pencil(),
)
return html.Div(
html.Class("inline-block"),
mo.HTML,
html.Div(
html.Class("inline-block tooltip tooltip-right"),
html.Data("tip", "Edit webhook"),
button,
),
)
}

View File

@@ -57,11 +57,17 @@ func listWebhooks(
html.Class("w-[40px]"),
html.Div(
html.Class("flex justify-start space-x-1"),
// editWebhookButton(webhook),
editWebhookButton(whook.ID),
// deleteWebhookButton(webhook.ID),
),
),
html.Td(component.SpanText(whook.Name)),
html.Td(
html.Div(
html.Class("flex items-center space-x-2"),
component.IsActivePing(whook.IsActive),
component.SpanText(whook.Name),
),
),
html.Td(component.SpanText(
func() string {
if name, ok := webhooks.FullEventTypes[whook.EventType]; ok {

View File

@@ -21,8 +21,10 @@ func MountRouter(
parent.GET("", h.indexPageHandler)
parent.GET("/list", h.listWebhooksHandler)
parent.GET("/create-form", h.createWebhookFormHandler)
parent.POST("", h.createWebhookHandler)
parent.GET("/create", h.createWebhookFormHandler)
parent.POST("/create", h.createWebhookHandler)
parent.GET("/:webhookID/edit", h.editWebhookFormHandler)
parent.POST("/:webhookID/edit", h.editWebhookHandler)
// parent.DELETE("/:webhookID", h.deleteWebhookHandler)
// parent.POST("/:webhookID/edit", h.editWebhookHandler)
}