feat: add Slack target type for incoming webhook notifications (#47)
All checks were successful
check / check (push) Successful in 4s

## Summary

Adds a new `slack` target type that sends webhook events as formatted messages to any Slack-compatible incoming webhook URL (Slack, Mattermost, and other compatible services).

closes #44

## What it does

When a webhook event is received, the Slack target:

1. Formats a human-readable message with event metadata (HTTP method, content type, timestamp, body size)
2. Pretty-prints the payload in a code block — JSON payloads get indented formatting, non-JSON payloads are shown as raw text
3. Truncates large payloads at 3500 characters to keep Slack messages reasonable
4. POSTs the message as a `{"text": "..."}` JSON payload to the configured webhook URL

## Changes

- **`internal/database/model_target.go`** — Add `TargetTypeSlack` constant
- **`internal/delivery/engine.go`** — Add `SlackTargetConfig` struct, `deliverSlack` method, `FormatSlackMessage` function (exported), `parseSlackConfig` helper. Route slack targets in `processDelivery` switch.
- **`internal/handlers/source_management.go`** — Handle `slack` type in `HandleTargetCreate`, building `webhook_url` config from the URL form field
- **`templates/source_detail.html`** — Add "Slack" option to target type dropdown with URL field and helper text
- **`README.md`** — Document the new target type, update roadmap

## Tests

- `TestParseSlackConfig_Valid` / `_Empty` / `_MissingWebhookURL` — Config parsing
- `TestFormatSlackMessage_JSONBody` / `_NonJSONBody` / `_EmptyBody` / `_LargeJSONTruncated` — Message formatting
- `TestDeliverSlack_Success` / `_Failure` / `_InvalidConfig` — End-to-end delivery
- `TestProcessDelivery_RoutesToSlack` — Routing from processDelivery switch

All existing tests continue to pass. `docker build .` (which runs `make check`) passes clean.

Co-authored-by: user <user@Mac.lan guest wan>
Reviewed-on: #47
Co-authored-by: clawbot <clawbot@noreply.example.org>
Co-committed-by: clawbot <clawbot@noreply.example.org>
This commit was merged in pull request #47.
This commit is contained in:
2026-03-17 12:30:50 +01:00
committed by Jeffrey Paul
parent 1fbcf96581
commit 8d702a16c6
6 changed files with 465 additions and 6 deletions

View File

@@ -291,7 +291,7 @@ events should be forwarded.
| `id` | UUID | Primary key |
| `webhook_id` | UUID | Foreign key → Webhook |
| `name` | string | Human-readable name |
| `type` | TargetType | One of: `http`, `database`, `log` |
| `type` | TargetType | One of: `http`, `slack`, `database`, `log` |
| `active` | boolean | Whether deliveries are enabled (default: true) |
| `config` | JSON text | Type-specific configuration |
| `max_retries` | integer | Maximum retry attempts for HTTP targets (0 = fire-and-forget, >0 = retries with backoff) |
@@ -463,6 +463,16 @@ target simply marks the delivery as immediately successful. The
per-webhook DB IS the dedicated event database — that's the whole point
of the database target type.
The **Slack target type** sends webhook events as formatted messages to
any Slack-compatible incoming webhook URL (works with Slack, Mattermost,
and other compatible services). Each message includes event metadata
(HTTP method, content type, timestamp, body size) and the payload
pretty-printed in a code block. JSON payloads are automatically
formatted with indentation for readability; non-JSON payloads are shown
as raw text. Large payloads are truncated to keep messages reasonable.
Config stores `webhook_url` — the Slack/Mattermost incoming webhook
endpoint.
The database uses the
[modernc.org/sqlite](https://pkg.go.dev/modernc.org/sqlite) driver at
runtime, though CGO is required at build time due to the transitive
@@ -601,8 +611,8 @@ fine — startup recovery rescans the database anyway).
**Scope:** Circuit breakers only apply to **HTTP targets with
`max_retries` > 0**. Fire-and-forget HTTP targets (`max_retries` == 0),
database targets (local operations), and log targets (stdout) do not use
circuit breakers.
Slack targets, database targets (local operations), and log
targets (stdout) do not use circuit breakers.
When a circuit is open and a new delivery arrives, the engine marks the
delivery as `retrying` and schedules a retry timer for after the
@@ -860,6 +870,8 @@ linted, tested, and compiled.
retries with exponential backoff when max_retries>0)
- [x] Implement database target type (store events in per-webhook DB)
- [x] Implement log target type (console output)
- [x] Implement Slack target type (Slack/Mattermost incoming webhook
notifications with pretty-printed payloads)
- [x] Webhook management pages (list, create, edit, delete)
- [x] Webhook request log viewer with pagination
- [x] Entrypoint and target management UI