fix: database target writes to dedicated archive table
All checks were successful
check / check (push) Successful in 1m43s

The "database" target type now writes events to a separate
archived_events table instead of just marking the delivery as done.
This table persists independently of internal event retention/pruning,
allowing the data to be consumed by external systems or preserved
indefinitely.

New ArchivedEvent model copies the full event payload (method, headers,
body, content_type) along with webhook/entrypoint/event/target IDs.
This commit is contained in:
clawbot
2026-03-01 16:40:05 -08:00
parent 418d3da97e
commit 6c393ccb78
4 changed files with 57 additions and 12 deletions

View File

@@ -0,0 +1,19 @@
package database
// ArchivedEvent stores webhook events delivered via the "database" target type.
// These records persist independently of internal event retention and pruning,
// providing a durable archive for downstream consumption.
type ArchivedEvent struct {
BaseModel
WebhookID string `gorm:"type:uuid;not null;index" json:"webhook_id"`
EntrypointID string `gorm:"type:uuid;not null" json:"entrypoint_id"`
EventID string `gorm:"type:uuid;not null" json:"event_id"`
TargetID string `gorm:"type:uuid;not null" json:"target_id"`
// Original request data (copied from Event at archive time)
Method string `gorm:"not null" json:"method"`
Headers string `gorm:"type:text" json:"headers"` // JSON
Body string `gorm:"type:text" json:"body"`
ContentType string `json:"content_type"`
}

View File

@@ -11,5 +11,6 @@ func (d *Database) Migrate() error {
&Event{}, &Event{},
&Delivery{}, &Delivery{},
&DeliveryResult{}, &DeliveryResult{},
&ArchivedEvent{},
) )
} }

View File

@@ -244,7 +244,32 @@ func (e *Engine) deliverRetry(_ context.Context, d *database.Delivery) {
} }
func (e *Engine) deliverDatabase(d *database.Delivery) { func (e *Engine) deliverDatabase(d *database.Delivery) {
// The event is already stored in the database; mark as delivered. // Write the event to the dedicated archived_events table. This table
// persists independently of internal event retention/pruning, so the
// data remains available for external consumption even after the
// original event is cleaned up.
archived := &database.ArchivedEvent{
WebhookID: d.Event.WebhookID,
EntrypointID: d.Event.EntrypointID,
EventID: d.EventID,
TargetID: d.TargetID,
Method: d.Event.Method,
Headers: d.Event.Headers,
Body: d.Event.Body,
ContentType: d.Event.ContentType,
}
if err := e.database.DB().Create(archived).Error; err != nil {
e.log.Error("failed to archive event",
"delivery_id", d.ID,
"event_id", d.EventID,
"error", err,
)
e.recordResult(d, 1, false, 0, "", err.Error(), 0)
e.updateDeliveryStatus(d, database.DeliveryStatusFailed)
return
}
e.recordResult(d, 1, true, 0, "", "", 0) e.recordResult(d, 1, true, 0, "", "", 0)
e.updateDeliveryStatus(d, database.DeliveryStatusDelivered) e.updateDeliveryStatus(d, database.DeliveryStatusDelivered)
} }