check / check (push) Successful in 3m14s
Pure code movement. Every declaration of internal/handlers/source_management.go moves unchanged into one of: webhook_list.go, webhook_create.go, webhook_detail.go, webhook_edit.go and webhook_delete.go for the webhook pages; event_log.go for the event log; entrypoint.go for the entrypoint handlers; target_create.go and target.go for the target handlers; and shared.go for the helpers several of them use. Only each file's package line and imports are new. Model: opus-5-5
171 lines
4.6 KiB
Go
171 lines
4.6 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// HandleSourceDelete handles webhook deletion.
|
|
func (h *Handlers) HandleSourceDelete() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
userID, ok := h.getUserID(r)
|
|
if !ok {
|
|
http.Redirect(
|
|
w, r, "/pages/login", http.StatusSeeOther,
|
|
)
|
|
|
|
return
|
|
}
|
|
|
|
sourceID := chi.URLParam(r, "sourceID")
|
|
|
|
var webhook database.Webhook
|
|
|
|
err := h.db.DB().Where(
|
|
"id = ? AND user_id = ?", sourceID, userID,
|
|
).First(&webhook).Error
|
|
if err != nil {
|
|
h.renderError(w, r, http.StatusNotFound)
|
|
|
|
return
|
|
}
|
|
|
|
h.deleteWebhookResources(w, r, webhook, userID)
|
|
}
|
|
}
|
|
|
|
// The messages deleteWebhookResources logs when a file of the event
|
|
// database cannot be removed: the database file itself, or only a
|
|
// sidecar once the database file is gone.
|
|
const (
|
|
eventDBLeftMsg = "webhook deleted, but its event database file is " +
|
|
"still on disk; remove it by hand"
|
|
sidecarLeftMsg = "webhook deleted and its events are gone, but a " +
|
|
"-wal or -shm sidecar of its event database is " +
|
|
"still on disk; remove it by hand"
|
|
)
|
|
|
|
// deleteWebhookResources soft-deletes config and hard-deletes
|
|
// the per-webhook event database.
|
|
func (h *Handlers) deleteWebhookResources(
|
|
w http.ResponseWriter,
|
|
r *http.Request,
|
|
webhook database.Webhook,
|
|
userID string,
|
|
) {
|
|
// The configuration delete commits before the event database
|
|
// is touched. No transaction spans the main database and the
|
|
// filesystem, so one side has to go first: committing the
|
|
// configuration first means a later failure leaves an unused
|
|
// event database file on disk, while removing the event
|
|
// database first would mean a failed commit destroys the
|
|
// history of a webhook that still exists. A leftover file can
|
|
// be removed by hand; deleted history cannot be recovered.
|
|
err := h.commitWebhookDeletion(&webhook)
|
|
if err != nil {
|
|
h.serverError(w, r, "failed to delete webhook", err)
|
|
|
|
return
|
|
}
|
|
|
|
h.log.Info(
|
|
"webhook deleted",
|
|
"webhook_id", webhook.ID,
|
|
"user_id", userID,
|
|
)
|
|
|
|
// Release the delivery engine's per-webhook archiving state
|
|
// so a deleted webhook's archive writer (and any handle open
|
|
// within its debounce window) does not linger for the
|
|
// process lifetime. The archive file itself is deliberately
|
|
// left on disk; see evictArchiveWriter.
|
|
h.evictArchiveWriter(webhook.ID)
|
|
|
|
err = h.dbMgr.DeleteDB(webhook.ID)
|
|
if err != nil {
|
|
// The configuration is committed, so the webhook is gone,
|
|
// but a file of its event database is still on disk with
|
|
// nothing referencing it. Report the failure rather than
|
|
// redirecting as though everything succeeded: the file
|
|
// needs removing by hand, and the logged error names it.
|
|
// When only a sidecar is left, the events are already
|
|
// gone, and the message must not suggest they survive.
|
|
msg := eventDBLeftMsg
|
|
if errors.Is(err, database.ErrSidecarNotRemoved) {
|
|
msg = sidecarLeftMsg
|
|
}
|
|
|
|
h.serverError(w, r, msg, err)
|
|
|
|
return
|
|
}
|
|
|
|
http.Redirect(
|
|
w, r, withNotice("/hooks", webhookDeleted), http.StatusSeeOther,
|
|
)
|
|
}
|
|
|
|
// commitWebhookDeletion soft-deletes a webhook's entrypoints,
|
|
// targets and the webhook row in one transaction. Every
|
|
// statement is checked and any failure rolls the whole
|
|
// transaction back, so a caller that gets an error knows the
|
|
// configuration is untouched and the event database must be
|
|
// left alone.
|
|
func (h *Handlers) commitWebhookDeletion(
|
|
webhook *database.Webhook,
|
|
) error {
|
|
tx := h.db.DB().Begin()
|
|
if tx.Error != nil {
|
|
return tx.Error
|
|
}
|
|
|
|
err := tx.Where(
|
|
"webhook_id = ?", webhook.ID,
|
|
).Delete(&database.Entrypoint{}).Error
|
|
if err != nil {
|
|
tx.Rollback()
|
|
|
|
return err
|
|
}
|
|
|
|
err = tx.Where(
|
|
"webhook_id = ?", webhook.ID,
|
|
).Delete(&database.Target{}).Error
|
|
if err != nil {
|
|
tx.Rollback()
|
|
|
|
return err
|
|
}
|
|
|
|
err = tx.Delete(webhook).Error
|
|
if err != nil {
|
|
tx.Rollback()
|
|
|
|
return err
|
|
}
|
|
|
|
return tx.Commit().Error
|
|
}
|
|
|
|
// evictArchiveWriter asks the delivery engine to drop the cached
|
|
// archive writers of a webhook's database targets, closing their
|
|
// archive file handles.
|
|
//
|
|
// The archive database files are NOT deleted. Unlike the event
|
|
// database — which is per-webhook working storage and is
|
|
// hard-deleted with the webhook — an archive is explicitly
|
|
// long-term storage that an operator may want to keep or move
|
|
// away for offline retention. Destroying it as a side effect of
|
|
// deleting a webhook would be a surprising and unrecoverable
|
|
// data loss, so the file is left for the operator to handle.
|
|
func (h *Handlers) evictArchiveWriter(webhookID string) {
|
|
if h.archives == nil {
|
|
return
|
|
}
|
|
|
|
h.archives.EvictWebhook(webhookID)
|
|
}
|