feat: implement core webhook engine, delivery system, and management UI (Phase 2)
All checks were successful
check / check (push) Successful in 1m49s
All checks were successful
check / check (push) Successful in 1m49s
- Webhook reception handler: look up entrypoint by UUID, verify active,
capture full HTTP request (method, headers, body, content-type), create
Event record, queue Delivery records for each active Target, return 200 OK.
Handles edge cases: unknown UUID → 404, inactive → 410, oversized → 413.
- Delivery engine (internal/delivery): fx-managed background goroutine that
polls for pending/retrying deliveries and dispatches to target type handlers.
Graceful shutdown via context cancellation.
- Target type implementations:
- HTTP: fire-and-forget POST with original headers forwarding
- Retry: exponential backoff (1s, 2s, 4s...) up to max_retries
- Database: immediate success (event already stored)
- Log: slog output with event details
- Webhook management pages with Tailwind CSS + Alpine.js:
- List (/sources): webhooks with entrypoint/target/event counts
- Create (/sources/new): form with auto-created default entrypoint
- Detail (/source/{id}): config, entrypoints, targets, recent events
- Edit (/source/{id}/edit): name, description, retention_days
- Delete (/source/{id}/delete): soft-delete with child records
- Add Entrypoint (/source/{id}/entrypoints): inline form
- Add Target (/source/{id}/targets): type-aware form
- Event Log (/source/{id}/logs): paginated with delivery status
- Updated README: marked completed items, updated naming conventions
table, added delivery engine to package layout and DI docs, updated
column names to reflect entity rename.
- Rebuilt Tailwind CSS for new template classes.
Part of: #15
This commit is contained in:
133
README.md
133
README.md
@@ -164,19 +164,14 @@ It uses:
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
This README uses the target naming scheme for the application's core
|
||||
entities. The current codebase uses older names that will be updated in
|
||||
a future refactor (see
|
||||
The codebase uses consistent naming throughout (rename completed in
|
||||
[issue #12](https://git.eeqj.de/sneak/webhooker/issues/12)):
|
||||
|
||||
| README (target name) | Current code name | Description |
|
||||
| --------------------- | ----------------- | ----------- |
|
||||
| **Webhook** | `Processor` | Top-level configuration entity grouping entrypoints and targets |
|
||||
| **Entrypoint** | `Webhook` | A receiver URL where external services POST events |
|
||||
| **Target** | `Target` | A delivery destination for events |
|
||||
|
||||
Throughout this document, the target names are used. The code rename is
|
||||
tracked separately.
|
||||
| Entity | Description |
|
||||
| ---------------- | ----------- |
|
||||
| **Webhook** | Top-level configuration entity grouping entrypoints and targets |
|
||||
| **Entrypoint** | A receiver URL where external services POST events |
|
||||
| **Target** | A delivery destination for events |
|
||||
|
||||
### Data Model
|
||||
|
||||
@@ -227,10 +222,10 @@ password logged to stdout.
|
||||
|
||||
#### Webhook
|
||||
|
||||
The top-level configuration entity (currently called "Processor" in
|
||||
code). A webhook groups together one or more entrypoints (receiver URLs)
|
||||
and one or more targets (delivery destinations) into a logical unit. A
|
||||
user creates a webhook to set up event routing.
|
||||
The top-level configuration entity. A webhook groups together one or
|
||||
more entrypoints (receiver URLs) and one or more targets (delivery
|
||||
destinations) into a logical unit. A user creates a webhook to set up
|
||||
event routing.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ---------------- | ------- | ----------- |
|
||||
@@ -247,15 +242,15 @@ webhook's dedicated database before automatic cleanup.
|
||||
|
||||
#### Entrypoint
|
||||
|
||||
A receiver URL where external services POST webhook events (currently
|
||||
called "Webhook" in code). Each entrypoint has a unique UUID-based path.
|
||||
A receiver URL where external services POST webhook events. Each
|
||||
entrypoint has a unique UUID-based path.
|
||||
When an HTTP request arrives at an entrypoint's path, webhooker captures
|
||||
the full request and creates an Event.
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------- | ------- | ----------- |
|
||||
| `id` | UUID | Primary key |
|
||||
| `processor_id` | UUID | Foreign key → Webhook |
|
||||
| `webhook_id` | UUID | Foreign key → Webhook |
|
||||
| `path` | string | Unique URL path (UUID-based, e.g. `/webhook/{uuid}`) |
|
||||
| `description` | string | Optional description |
|
||||
| `active` | boolean | Whether this entrypoint accepts events (default: true) |
|
||||
@@ -275,7 +270,7 @@ events should be forwarded.
|
||||
| Field | Type | Description |
|
||||
| ---------------- | ---------- | ----------- |
|
||||
| `id` | UUID | Primary key |
|
||||
| `processor_id` | UUID | Foreign key → Webhook |
|
||||
| `webhook_id` | UUID | Foreign key → Webhook |
|
||||
| `name` | string | Human-readable name |
|
||||
| `type` | TargetType | One of: `http`, `retry`, `database`, `log` |
|
||||
| `active` | boolean | Whether deliveries are enabled (default: true) |
|
||||
@@ -320,9 +315,9 @@ data for replay and auditing.
|
||||
|
||||
| Field | Type | Description |
|
||||
| -------------- | ------ | ----------- |
|
||||
| `id` | UUID | Primary key |
|
||||
| `processor_id` | UUID | Foreign key → Webhook |
|
||||
| `webhook_id` | UUID | Foreign key → Entrypoint |
|
||||
| `id` | UUID | Primary key |
|
||||
| `webhook_id` | UUID | Foreign key → Webhook |
|
||||
| `entrypoint_id` | UUID | Foreign key → Entrypoint |
|
||||
| `method` | string | HTTP method (POST, PUT, etc.) |
|
||||
| `headers` | JSON | Complete request headers |
|
||||
| `body` | text | Raw request body |
|
||||
@@ -406,8 +401,8 @@ configuration data and per-webhook databases for event storage.
|
||||
**Main Application Database** — will store:
|
||||
|
||||
- **Users** — accounts and Argon2id password hashes
|
||||
- **Webhooks** (Processors) — webhook configurations
|
||||
- **Entrypoints** (Webhooks) — receiver URL definitions
|
||||
- **Webhooks** — webhook configurations
|
||||
- **Entrypoints** — receiver URL definitions
|
||||
- **Targets** — delivery destination configurations
|
||||
- **APIKeys** — programmatic access credentials
|
||||
|
||||
@@ -515,6 +510,8 @@ against a misbehaving sender).
|
||||
| `POST` | `/source/{id}/edit` | Edit webhook submission |
|
||||
| `POST` | `/source/{id}/delete` | Delete webhook |
|
||||
| `GET` | `/source/{id}/logs` | Webhook event logs |
|
||||
| `POST` | `/source/{id}/entrypoints` | Add entrypoint to webhook |
|
||||
| `POST` | `/source/{id}/targets` | Add target to webhook |
|
||||
|
||||
#### Infrastructure Endpoints
|
||||
|
||||
@@ -554,8 +551,8 @@ webhooker/
|
||||
│ │ ├── database.go # GORM connection, migrations, admin seed
|
||||
│ │ ├── models.go # AutoMigrate for all models
|
||||
│ │ ├── model_user.go # User entity
|
||||
│ │ ├── model_processor.go # Webhook entity (to be renamed)
|
||||
│ │ ├── model_webhook.go # Entrypoint entity (to be renamed)
|
||||
│ │ ├── model_webhook.go # Webhook entity
|
||||
│ │ ├── model_entrypoint.go # Entrypoint entity
|
||||
│ │ ├── model_target.go # Target entity and TargetType enum
|
||||
│ │ ├── model_event.go # Event entity
|
||||
│ │ ├── model_delivery.go # Delivery entity and DeliveryStatus enum
|
||||
@@ -564,13 +561,15 @@ webhooker/
|
||||
│ │ └── password.go # Argon2id hashing and verification
|
||||
│ ├── globals/
|
||||
│ │ └── globals.go # Build-time variables (appname, version, arch)
|
||||
│ ├── delivery/
|
||||
│ │ └── engine.go # Background delivery engine (fx lifecycle)
|
||||
│ ├── handlers/
|
||||
│ │ ├── handlers.go # Base handler struct, JSON helpers, template rendering
|
||||
│ │ ├── auth.go # Login, logout handlers
|
||||
│ │ ├── healthcheck.go # Health check handler
|
||||
│ │ ├── index.go # Index page handler
|
||||
│ │ ├── profile.go # User profile handler
|
||||
│ │ ├── source_management.go # Webhook CRUD handlers (stubs)
|
||||
│ │ ├── source_management.go # Webhook CRUD handlers
|
||||
│ │ └── webhook.go # Webhook receiver handler
|
||||
│ ├── healthcheck/
|
||||
│ │ └── healthcheck.go # Health check service (uptime, version)
|
||||
@@ -610,10 +609,11 @@ Components are wired via Uber fx in this order:
|
||||
6. `session.New` — Cookie-based session manager
|
||||
7. `handlers.New` — HTTP handlers
|
||||
8. `middleware.New` — HTTP middleware
|
||||
9. `server.New` — HTTP server and router
|
||||
9. `delivery.New` — Background delivery engine
|
||||
10. `server.New` — HTTP server and router
|
||||
|
||||
The server starts via `fx.Invoke(func(*server.Server) {})` which
|
||||
triggers the fx lifecycle hooks in dependency order.
|
||||
The server starts via `fx.Invoke(func(*server.Server, *delivery.Engine)
|
||||
{})` which triggers the fx lifecycle hooks in dependency order.
|
||||
|
||||
### Middleware Stack
|
||||
|
||||
@@ -669,58 +669,57 @@ linted, tested, and compiled.
|
||||
|
||||
## TODO
|
||||
|
||||
### Phase 1: Core Webhook Engine
|
||||
- [ ] Implement webhook reception and event storage at `/webhook/{uuid}`
|
||||
- [ ] Build event processing and target delivery engine
|
||||
- [ ] Implement HTTP target type (fire-and-forget POST)
|
||||
- [ ] Implement retry target type (exponential backoff)
|
||||
- [ ] Implement database target type (store only)
|
||||
- [ ] Implement log target type (console output)
|
||||
### Completed: Code Quality (Phase 1 of MVP)
|
||||
- [x] Rename Processor → Webhook, Webhook → Entrypoint in code
|
||||
([#12](https://git.eeqj.de/sneak/webhooker/issues/12))
|
||||
- [x] Embed templates via `//go:embed`
|
||||
([#7](https://git.eeqj.de/sneak/webhooker/issues/7))
|
||||
- [x] Use `slog.LevelVar` for dynamic log level switching
|
||||
([#8](https://git.eeqj.de/sneak/webhooker/issues/8))
|
||||
- [x] Simplify configuration to prefer environment variables
|
||||
([#10](https://git.eeqj.de/sneak/webhooker/issues/10))
|
||||
- [x] Remove redundant `godotenv/autoload` import
|
||||
([#11](https://git.eeqj.de/sneak/webhooker/issues/11))
|
||||
- [x] Implement authentication middleware for protected routes
|
||||
([#9](https://git.eeqj.de/sneak/webhooker/issues/9))
|
||||
- [x] Replace Bootstrap with Tailwind CSS + Alpine.js
|
||||
([#4](https://git.eeqj.de/sneak/webhooker/issues/4))
|
||||
|
||||
### Completed: Core Webhook Engine (Phase 2 of MVP)
|
||||
- [x] Implement webhook reception and event storage at `/webhook/{uuid}`
|
||||
- [x] Build event processing and target delivery engine
|
||||
- [x] Implement HTTP target type (fire-and-forget POST)
|
||||
- [x] Implement retry target type (exponential backoff)
|
||||
- [x] Implement database target type (store only)
|
||||
- [x] Implement log target type (console output)
|
||||
- [x] Webhook management pages (list, create, edit, delete)
|
||||
- [x] Webhook request log viewer with pagination
|
||||
- [x] Entrypoint and target management UI
|
||||
|
||||
### Remaining: Core Features
|
||||
- [ ] Per-webhook rate limiting in the receiver handler
|
||||
- [ ] Webhook signature verification (GitHub, Stripe formats)
|
||||
|
||||
### Phase 2: Database Separation
|
||||
- [ ] Split into main application DB + per-webhook event DBs
|
||||
- [ ] Automatic event retention cleanup based on `retention_days`
|
||||
- [ ] Per-webhook database lifecycle management (create on webhook
|
||||
creation, delete on webhook removal)
|
||||
|
||||
### Phase 3: Security & Infrastructure
|
||||
- [ ] Implement authentication middleware for protected routes
|
||||
([#9](https://git.eeqj.de/sneak/webhooker/issues/9))
|
||||
- [ ] Security headers (HSTS, CSP, X-Frame-Options)
|
||||
- [ ] CSRF protection for forms
|
||||
- [ ] Session expiration and "remember me"
|
||||
- [ ] Password change/reset flow
|
||||
- [ ] API key authentication for programmatic access
|
||||
|
||||
### Phase 4: Web UI
|
||||
- [ ] Webhook management pages (list, create, edit, delete)
|
||||
- [ ] Webhook request log viewer with filtering
|
||||
- [ ] Delivery status and retry management UI
|
||||
- [ ] Manual event redelivery
|
||||
- [ ] Analytics dashboard (success rates, response times)
|
||||
- [ ] Replace Bootstrap with Tailwind CSS + Alpine.js
|
||||
([#4](https://git.eeqj.de/sneak/webhooker/issues/4))
|
||||
- [ ] Delivery status and retry management UI
|
||||
|
||||
### Phase 5: REST API
|
||||
### Remaining: Database Separation
|
||||
- [ ] Split into main application DB + per-webhook event DBs
|
||||
- [ ] Automatic event retention cleanup based on `retention_days`
|
||||
- [ ] Per-webhook database lifecycle management (create on webhook
|
||||
creation, delete on webhook removal)
|
||||
|
||||
### Remaining: REST API
|
||||
- [ ] RESTful CRUD for webhooks, entrypoints, targets
|
||||
- [ ] Event viewing and filtering endpoints
|
||||
- [ ] Event redelivery endpoint
|
||||
- [ ] OpenAPI specification
|
||||
|
||||
### Phase 6: Code Quality
|
||||
- [ ] Rename Processor → Webhook, Webhook → Entrypoint in code
|
||||
([#12](https://git.eeqj.de/sneak/webhooker/issues/12))
|
||||
- [ ] Embed templates via `//go:embed`
|
||||
([#7](https://git.eeqj.de/sneak/webhooker/issues/7))
|
||||
- [ ] Use `slog.LevelVar` for dynamic log level switching
|
||||
([#8](https://git.eeqj.de/sneak/webhooker/issues/8))
|
||||
- [ ] Simplify configuration to prefer environment variables
|
||||
([#10](https://git.eeqj.de/sneak/webhooker/issues/10))
|
||||
- [ ] Remove redundant `godotenv/autoload` import
|
||||
([#11](https://git.eeqj.de/sneak/webhooker/issues/11))
|
||||
|
||||
### Future
|
||||
- [ ] Email delivery target type
|
||||
- [ ] SNS, S3, Slack delivery targets
|
||||
|
||||
Reference in New Issue
Block a user