diff --git a/internal/database/model_archived_event.go b/internal/database/model_archived_event.go new file mode 100644 index 0000000..9f75d23 --- /dev/null +++ b/internal/database/model_archived_event.go @@ -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"` +} diff --git a/internal/database/models.go b/internal/database/models.go index ce19b36..23dea14 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -11,5 +11,6 @@ func (d *Database) Migrate() error { &Event{}, &Delivery{}, &DeliveryResult{}, + &ArchivedEvent{}, ) } diff --git a/internal/delivery/engine.go b/internal/delivery/engine.go index 41599d7..a2c2e0d 100644 --- a/internal/delivery/engine.go +++ b/internal/delivery/engine.go @@ -244,7 +244,32 @@ func (e *Engine) deliverRetry(_ context.Context, 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.updateDeliveryStatus(d, database.DeliveryStatusDelivered) } diff --git a/internal/server/routes.go b/internal/server/routes.go index 34ec050..9e80ecd 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -100,17 +100,17 @@ func (s *Server) SetupRoutes() { s.router.Route("/source/{sourceID}", func(r chi.Router) { r.Use(s.mw.RequireAuth()) - r.Get("/", s.h.HandleSourceDetail()) // View webhook details - r.Get("/edit", s.h.HandleSourceEdit()) // Show edit form - r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission - r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook - r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs - r.Post("/entrypoints", s.h.HandleEntrypointCreate()) // Add entrypoint - r.Post("/entrypoints/{entrypointID}/delete", s.h.HandleEntrypointDelete()) // Delete entrypoint - r.Post("/entrypoints/{entrypointID}/toggle", s.h.HandleEntrypointToggle()) // Toggle entrypoint active - r.Post("/targets", s.h.HandleTargetCreate()) // Add target - r.Post("/targets/{targetID}/delete", s.h.HandleTargetDelete()) // Delete target - r.Post("/targets/{targetID}/toggle", s.h.HandleTargetToggle()) // Toggle target active + r.Get("/", s.h.HandleSourceDetail()) // View webhook details + r.Get("/edit", s.h.HandleSourceEdit()) // Show edit form + r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission + r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook + r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs + r.Post("/entrypoints", s.h.HandleEntrypointCreate()) // Add entrypoint + r.Post("/entrypoints/{entrypointID}/delete", s.h.HandleEntrypointDelete()) // Delete entrypoint + r.Post("/entrypoints/{entrypointID}/toggle", s.h.HandleEntrypointToggle()) // Toggle entrypoint active + r.Post("/targets", s.h.HandleTargetCreate()) // Add target + r.Post("/targets/{targetID}/delete", s.h.HandleTargetDelete()) // Delete target + r.Post("/targets/{targetID}/toggle", s.h.HandleTargetToggle()) // Toggle target active }) // Entrypoint endpoint — accepts incoming webhook POST requests only.