Split source_management.go along its CRUD seams (closes #274)
check / check (push) Successful in 3m14s
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
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user