feat: webhooker 1.0 MVP — entity rename, core engine, delivery, management UI #16
14
internal/database/model_entrypoint.go
Normal file
14
internal/database/model_entrypoint.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
// Entrypoint represents an inbound URL endpoint that feeds into a webhook
|
||||||
|
type Entrypoint struct {
|
||||||
|
BaseModel
|
||||||
|
|
||||||
|
WebhookID string `gorm:"type:uuid;not null" json:"webhook_id"`
|
||||||
|
Path string `gorm:"uniqueIndex;not null" json:"path"` // URL path for this entrypoint
|
||||||
|
Description string `json:"description"`
|
||||||
|
Active bool `gorm:"default:true" json:"active"`
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
Webhook Webhook `json:"webhook,omitempty"`
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
// Event represents a webhook event
|
// Event represents a captured webhook event
|
||||||
type Event struct {
|
type Event struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
ProcessorID string `gorm:"type:uuid;not null" json:"processor_id"`
|
WebhookID string `gorm:"type:uuid;not null" json:"webhook_id"`
|
||||||
WebhookID string `gorm:"type:uuid;not null" json:"webhook_id"`
|
EntrypointID string `gorm:"type:uuid;not null" json:"entrypoint_id"`
|
||||||
|
|
||||||
// Request data
|
// Request data
|
||||||
Method string `gorm:"not null" json:"method"`
|
Method string `gorm:"not null" json:"method"`
|
||||||
@@ -14,7 +14,7 @@ type Event struct {
|
|||||||
ContentType string `json:"content_type"`
|
ContentType string `json:"content_type"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Processor Processor `json:"processor,omitempty"`
|
|
||||||
Webhook Webhook `json:"webhook,omitempty"`
|
Webhook Webhook `json:"webhook,omitempty"`
|
||||||
|
Entrypoint Entrypoint `json:"entrypoint,omitempty"`
|
||||||
Deliveries []Delivery `json:"deliveries,omitempty"`
|
Deliveries []Delivery `json:"deliveries,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
// Processor represents an event processor
|
|
||||||
type Processor struct {
|
|
||||||
BaseModel
|
|
||||||
|
|
||||||
UserID string `gorm:"type:uuid;not null" json:"user_id"`
|
|
||||||
Name string `gorm:"not null" json:"name"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
RetentionDays int `gorm:"default:30" json:"retention_days"` // Days to retain events
|
|
||||||
|
|
||||||
// Relations
|
|
||||||
User User `json:"user,omitempty"`
|
|
||||||
Webhooks []Webhook `json:"webhooks,omitempty"`
|
|
||||||
Targets []Target `json:"targets,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -10,14 +10,14 @@ const (
|
|||||||
TargetTypeLog TargetType = "log"
|
TargetTypeLog TargetType = "log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Target represents a delivery target for a processor
|
// Target represents a delivery target for a webhook
|
||||||
type Target struct {
|
type Target struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
ProcessorID string `gorm:"type:uuid;not null" json:"processor_id"`
|
WebhookID string `gorm:"type:uuid;not null" json:"webhook_id"`
|
||||||
Name string `gorm:"not null" json:"name"`
|
Name string `gorm:"not null" json:"name"`
|
||||||
Type TargetType `gorm:"not null" json:"type"`
|
Type TargetType `gorm:"not null" json:"type"`
|
||||||
Active bool `gorm:"default:true" json:"active"`
|
Active bool `gorm:"default:true" json:"active"`
|
||||||
|
|
||||||
// Configuration fields (JSON stored based on type)
|
// Configuration fields (JSON stored based on type)
|
||||||
Config string `gorm:"type:text" json:"config"` // JSON configuration
|
Config string `gorm:"type:text" json:"config"` // JSON configuration
|
||||||
@@ -27,6 +27,6 @@ type Target struct {
|
|||||||
MaxQueueSize int `json:"max_queue_size,omitempty"`
|
MaxQueueSize int `json:"max_queue_size,omitempty"`
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Processor Processor `json:"processor,omitempty"`
|
Webhook Webhook `json:"webhook,omitempty"`
|
||||||
Deliveries []Delivery `json:"deliveries,omitempty"`
|
Deliveries []Delivery `json:"deliveries,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ type User struct {
|
|||||||
Password string `gorm:"not null" json:"-"` // Argon2 hashed
|
Password string `gorm:"not null" json:"-"` // Argon2 hashed
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Processors []Processor `json:"processors,omitempty"`
|
Webhooks []Webhook `json:"webhooks,omitempty"`
|
||||||
APIKeys []APIKey `json:"api_keys,omitempty"`
|
APIKeys []APIKey `json:"api_keys,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
// Webhook represents a webhook endpoint that feeds into a processor
|
// Webhook represents a webhook processing unit that groups entrypoints and targets
|
||||||
type Webhook struct {
|
type Webhook struct {
|
||||||
BaseModel
|
BaseModel
|
||||||
|
|
||||||
ProcessorID string `gorm:"type:uuid;not null" json:"processor_id"`
|
UserID string `gorm:"type:uuid;not null" json:"user_id"`
|
||||||
Path string `gorm:"uniqueIndex;not null" json:"path"` // URL path for this webhook
|
Name string `gorm:"not null" json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Active bool `gorm:"default:true" json:"active"`
|
RetentionDays int `gorm:"default:30" json:"retention_days"` // Days to retain events
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
Processor Processor `json:"processor,omitempty"`
|
User User `json:"user,omitempty"`
|
||||||
|
Entrypoints []Entrypoint `json:"entrypoints,omitempty"`
|
||||||
|
Targets []Target `json:"targets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ func (d *Database) Migrate() error {
|
|||||||
return d.db.AutoMigrate(
|
return d.db.AutoMigrate(
|
||||||
&User{},
|
&User{},
|
||||||
&APIKey{},
|
&APIKey{},
|
||||||
&Processor{},
|
|
||||||
&Webhook{},
|
&Webhook{},
|
||||||
|
&Entrypoint{},
|
||||||
&Target{},
|
&Target{},
|
||||||
&Event{},
|
&Event{},
|
||||||
&Delivery{},
|
&Delivery{},
|
||||||
|
|||||||
@@ -4,66 +4,66 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleSourceList shows a list of user's webhook sources
|
// HandleSourceList shows a list of user's webhooks
|
||||||
func (h *Handlers) HandleSourceList() http.HandlerFunc {
|
func (h *Handlers) HandleSourceList() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source list page
|
// TODO: Implement webhook list page
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceCreate shows the form to create a new webhook source
|
// HandleSourceCreate shows the form to create a new webhook
|
||||||
func (h *Handlers) HandleSourceCreate() http.HandlerFunc {
|
func (h *Handlers) HandleSourceCreate() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source creation form
|
// TODO: Implement webhook creation form
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceCreateSubmit handles the source creation form submission
|
// HandleSourceCreateSubmit handles the webhook creation form submission
|
||||||
func (h *Handlers) HandleSourceCreateSubmit() http.HandlerFunc {
|
func (h *Handlers) HandleSourceCreateSubmit() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source creation logic
|
// TODO: Implement webhook creation logic
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceDetail shows details for a specific webhook source
|
// HandleSourceDetail shows details for a specific webhook
|
||||||
func (h *Handlers) HandleSourceDetail() http.HandlerFunc {
|
func (h *Handlers) HandleSourceDetail() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source detail page
|
// TODO: Implement webhook detail page
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceEdit shows the form to edit a webhook source
|
// HandleSourceEdit shows the form to edit a webhook
|
||||||
func (h *Handlers) HandleSourceEdit() http.HandlerFunc {
|
func (h *Handlers) HandleSourceEdit() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source edit form
|
// TODO: Implement webhook edit form
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceEditSubmit handles the source edit form submission
|
// HandleSourceEditSubmit handles the webhook edit form submission
|
||||||
func (h *Handlers) HandleSourceEditSubmit() http.HandlerFunc {
|
func (h *Handlers) HandleSourceEditSubmit() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source update logic
|
// TODO: Implement webhook update logic
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceDelete handles webhook source deletion
|
// HandleSourceDelete handles webhook deletion
|
||||||
func (h *Handlers) HandleSourceDelete() http.HandlerFunc {
|
func (h *Handlers) HandleSourceDelete() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source deletion logic
|
// TODO: Implement webhook deletion logic
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleSourceLogs shows the request/response logs for a webhook source
|
// HandleSourceLogs shows the request/response logs for a webhook
|
||||||
func (h *Handlers) HandleSourceLogs() http.HandlerFunc {
|
func (h *Handlers) HandleSourceLogs() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: Implement source logs page
|
// TODO: Implement webhook logs page
|
||||||
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
http.Error(w, "Not implemented", http.StatusNotImplemented)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,19 @@ import (
|
|||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HandleWebhook handles incoming webhook requests
|
// HandleWebhook handles incoming webhook requests at entrypoint URLs
|
||||||
func (h *Handlers) HandleWebhook() http.HandlerFunc {
|
func (h *Handlers) HandleWebhook() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get webhook UUID from URL
|
// Get entrypoint UUID from URL
|
||||||
webhookUUID := chi.URLParam(r, "uuid")
|
entrypointUUID := chi.URLParam(r, "uuid")
|
||||||
if webhookUUID == "" {
|
if entrypointUUID == "" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the incoming webhook request
|
// Log the incoming webhook request
|
||||||
h.log.Info("webhook request received",
|
h.log.Info("webhook request received",
|
||||||
"uuid", webhookUUID,
|
"entrypoint_uuid", entrypointUUID,
|
||||||
"method", r.Method,
|
"method", r.Method,
|
||||||
"remote_addr", r.RemoteAddr,
|
"remote_addr", r.RemoteAddr,
|
||||||
"user_agent", r.UserAgent(),
|
"user_agent", r.UserAgent(),
|
||||||
@@ -32,7 +32,7 @@ func (h *Handlers) HandleWebhook() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement webhook handling logic
|
// TODO: Implement webhook handling logic
|
||||||
// For now, return "unimplemented" for all webhook POST requests
|
// Look up entrypoint by UUID, find parent webhook, fan out to targets
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
_, err := w.Write([]byte("unimplemented"))
|
_, err := w.Write([]byte("unimplemented"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -90,23 +90,23 @@ func (s *Server) SetupRoutes() {
|
|||||||
r.Get("/", s.h.HandleProfile())
|
r.Get("/", s.h.HandleProfile())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Webhook source management routes (require authentication)
|
// Webhook management routes (require authentication)
|
||||||
s.router.Route("/sources", func(r chi.Router) {
|
s.router.Route("/sources", func(r chi.Router) {
|
||||||
// TODO: Add authentication middleware here
|
// TODO: Add authentication middleware here
|
||||||
r.Get("/", s.h.HandleSourceList()) // List all sources
|
r.Get("/", s.h.HandleSourceList()) // List all webhooks
|
||||||
r.Get("/new", s.h.HandleSourceCreate()) // Show create form
|
r.Get("/new", s.h.HandleSourceCreate()) // Show create form
|
||||||
r.Post("/new", s.h.HandleSourceCreateSubmit()) // Handle create submission
|
r.Post("/new", s.h.HandleSourceCreateSubmit()) // Handle create submission
|
||||||
})
|
})
|
||||||
|
|
||||||
s.router.Route("/source/{sourceID}", func(r chi.Router) {
|
s.router.Route("/source/{sourceID}", func(r chi.Router) {
|
||||||
// TODO: Add authentication middleware here
|
// TODO: Add authentication middleware here
|
||||||
r.Get("/", s.h.HandleSourceDetail()) // View source details
|
r.Get("/", s.h.HandleSourceDetail()) // View webhook details
|
||||||
r.Get("/edit", s.h.HandleSourceEdit()) // Show edit form
|
r.Get("/edit", s.h.HandleSourceEdit()) // Show edit form
|
||||||
r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission
|
r.Post("/edit", s.h.HandleSourceEditSubmit()) // Handle edit submission
|
||||||
r.Post("/delete", s.h.HandleSourceDelete()) // Delete source
|
r.Post("/delete", s.h.HandleSourceDelete()) // Delete webhook
|
||||||
r.Get("/logs", s.h.HandleSourceLogs()) // View source logs
|
r.Get("/logs", s.h.HandleSourceLogs()) // View webhook logs
|
||||||
})
|
})
|
||||||
|
|
||||||
// Webhook endpoint - accepts all HTTP methods
|
// Entrypoint endpoint - accepts incoming webhook POST requests
|
||||||
s.router.HandleFunc("/webhook/{uuid}", s.h.HandleWebhook())
|
s.router.HandleFunc("/webhook/{uuid}", s.h.HandleWebhook())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user