feat: bring repo up to REPO_POLICIES standards #6
15
.dockerignore
Normal file
15
.dockerignore
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
.git/
|
||||||
|
bin/
|
||||||
|
*.md
|
||||||
|
LICENSE
|
||||||
|
.editorconfig
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
*.sqlite3
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
15
.gitea/workflows/check.yml
Normal file
15
.gitea/workflows/check.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
name: check
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 2024-10-23
|
||||||
|
- name: Build Docker image (runs make check)
|
||||||
|
run: docker build .
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -4,7 +4,7 @@
|
|||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
bin/
|
bin/
|
||||||
webhooker
|
/webhooker
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
@@ -26,9 +26,10 @@ vendor/
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Environment files
|
# Environment and config files
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
|
config.yaml
|
||||||
|
|
||||||
# Database files
|
# Database files
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
65
Dockerfile
65
Dockerfile
@@ -1,58 +1,69 @@
|
|||||||
## lint image
|
# golang:1.24 (bookworm) — 2026-03-01
|
||||||
FROM golangci/golangci-lint:latest
|
# Using Debian-based image because gorm.io/driver/sqlite pulls in
|
||||||
|
# mattn/go-sqlite3 (CGO), which does not compile on Alpine musl.
|
||||||
|
FROM golang@sha256:d2d2bc1c84f7e60d7d2438a3836ae7d0c847f4888464e7ec9ba3a1339a1ee804 AS builder
|
||||||
|
|
||||||
RUN mkdir -p /build
|
# gcc is pre-installed in the Debian-based golang image
|
||||||
WORKDIR /build
|
RUN apt-get update && apt-get install -y --no-install-recommends make && rm -rf /var/lib/apt/lists/*
|
||||||
COPY ./ ./
|
|
||||||
RUN golangci-lint run
|
|
||||||
|
|
||||||
## build image:
|
|
||||||
FROM golang:1.22-alpine AS builder
|
|
||||||
|
|
||||||
# Install build dependencies including gcc for CGO
|
|
||||||
RUN apk add --no-cache git make gcc musl-dev sqlite-dev
|
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
# Copy go mod files
|
# Install golangci-lint v1.64.8 — 2026-03-01
|
||||||
|
# Using v1.x because the repo's .golangci.yml uses v1 config format.
|
||||||
|
RUN set -eux; \
|
||||||
|
GOLANGCI_VERSION="1.64.8"; \
|
||||||
|
ARCH="$(uname -m)"; \
|
||||||
|
case "${ARCH}" in \
|
||||||
|
x86_64) \
|
||||||
|
GOARCH="amd64"; \
|
||||||
|
GOLANGCI_SHA256="b6270687afb143d019f387c791cd2a6f1cb383be9b3124d241ca11bd3ce2e54e"; \
|
||||||
|
;; \
|
||||||
|
aarch64) \
|
||||||
|
GOARCH="arm64"; \
|
||||||
|
GOLANGCI_SHA256="a6ab58ebcb1c48572622146cdaec2956f56871038a54ed1149f1386e287789a5"; \
|
||||||
|
;; \
|
||||||
|
*) echo "unsupported architecture: ${ARCH}" && exit 1 ;; \
|
||||||
|
esac; \
|
||||||
|
wget -q "https://github.com/golangci/golangci-lint/releases/download/v${GOLANGCI_VERSION}/golangci-lint-${GOLANGCI_VERSION}-linux-${GOARCH}.tar.gz" \
|
||||||
|
-O /tmp/golangci-lint.tar.gz; \
|
||||||
|
echo "${GOLANGCI_SHA256} /tmp/golangci-lint.tar.gz" | sha256sum -c -; \
|
||||||
|
tar -xzf /tmp/golangci-lint.tar.gz -C /tmp; \
|
||||||
|
mv "/tmp/golangci-lint-${GOLANGCI_VERSION}-linux-${GOARCH}/golangci-lint" /usr/local/bin/; \
|
||||||
|
rm -rf /tmp/golangci-lint*; \
|
||||||
|
golangci-lint --version
|
||||||
|
|
||||||
|
# Copy go module files and download dependencies
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
|
COPY pkg/config/go.mod pkg/config/go.sum ./pkg/config/
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
# Copy source code
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the application with CGO enabled for SQLite
|
# Run all checks (fmt-check, lint, test, build)
|
||||||
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o webhooker cmd/webhooker/main.go
|
RUN make check
|
||||||
|
|
||||||
## output image:
|
# alpine:3.21 — 2026-03-01
|
||||||
FROM alpine:latest
|
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709
|
||||||
|
|
||||||
# Install ca-certificates for HTTPS and sqlite libs
|
RUN apk --no-cache add ca-certificates
|
||||||
RUN apk --no-cache add ca-certificates sqlite-libs
|
|
||||||
|
|
||||||
# Create non-root user
|
# Create non-root user
|
||||||
RUN addgroup -g 1000 -S webhooker && \
|
RUN addgroup -g 1000 -S webhooker && \
|
||||||
adduser -u 1000 -S webhooker -G webhooker
|
adduser -u 1000 -S webhooker -G webhooker
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy binary from builder
|
# Copy binary from builder
|
||||||
COPY --from=builder /build/webhooker .
|
COPY --from=builder /build/bin/webhooker .
|
||||||
|
|
||||||
# Change ownership
|
|
||||||
RUN chown -R webhooker:webhooker /app
|
RUN chown -R webhooker:webhooker /app
|
||||||
|
|
||||||
# Switch to non-root user
|
|
||||||
USER webhooker
|
USER webhooker
|
||||||
|
|
||||||
# Expose port
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
# Health check
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/.well-known/healthcheck.json || exit 1
|
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/.well-known/healthcheck.json || exit 1
|
||||||
|
|
||||||
# Run the application
|
|
||||||
CMD ["./webhooker"]
|
CMD ["./webhooker"]
|
||||||
|
|||||||
46
Makefile
46
Makefile
@@ -1,39 +1,43 @@
|
|||||||
.PHONY: test fmt lint build run clean
|
.PHONY: test lint fmt fmt-check check build run dev deps docker clean hooks
|
||||||
|
|
||||||
# Default target
|
# Default target
|
||||||
test: lint
|
.DEFAULT_GOAL := check
|
||||||
go test -v ./...
|
|
||||||
|
|
||||||
fmt:
|
test:
|
||||||
go fmt ./...
|
go test -v -race -timeout 30s ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run
|
golangci-lint run --config .golangci.yml ./...
|
||||||
|
|
||||||
build: test
|
fmt:
|
||||||
go build -o bin/webhooker cmd/webhooker/main.go
|
gofmt -s -w .
|
||||||
|
@command -v goimports >/dev/null 2>&1 && goimports -w . || true
|
||||||
|
|
||||||
|
fmt-check:
|
||||||
|
@test -z "$$(gofmt -s -l .)" || { echo "gofmt needed on:"; gofmt -s -l .; exit 1; }
|
||||||
|
|
||||||
|
check: fmt-check lint test build
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -o bin/webhooker ./cmd/webhooker
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./bin/webhooker
|
./bin/webhooker
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf bin/
|
|
||||||
|
|
||||||
# Development helpers
|
|
||||||
.PHONY: dev deps
|
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
go run cmd/webhooker/main.go
|
go run ./cmd/webhooker
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
go mod download
|
go mod download
|
||||||
go mod tidy
|
go mod tidy
|
||||||
|
|
||||||
# Docker targets
|
docker:
|
||||||
.PHONY: docker-build docker-run
|
|
||||||
|
|
||||||
docker-build:
|
|
||||||
docker build -t webhooker:latest .
|
docker build -t webhooker:latest .
|
||||||
|
|
||||||
docker-run: docker-build
|
clean:
|
||||||
docker run --rm -p 8080:8080 webhooker:latest
|
rm -rf bin/
|
||||||
|
|
||||||
|
hooks:
|
||||||
|
@printf '#!/bin/sh\nmake check\n' > .git/hooks/pre-commit
|
||||||
|
@chmod +x .git/hooks/pre-commit
|
||||||
|
@echo "pre-commit hook installed"
|
||||||
|
|||||||
435
README.md
435
README.md
@@ -1,315 +1,156 @@
|
|||||||
# webhooker
|
# webhooker
|
||||||
|
|
||||||
webhooker is, at its core, a webhook proxy. it receives webhooks and stores
|
webhooker is a Go web application by [@sneak](https://sneak.berlin) that
|
||||||
them in a database, then delivers them (either fire-and-forget, or with
|
receives, stores, and proxies webhooks to configured targets with retry
|
||||||
retries until successful) to a configured endpoint.
|
support, observability, and a management web UI. License: pending.
|
||||||
|
|
||||||
webhook endpoints are inputs to an event processor. there can be multiple
|
## Getting Started
|
||||||
webhook endpoint urls configured as inputs to any given processor. each
|
|
||||||
processor can have 0 or more targets. all events delivered to the event
|
|
||||||
processor will be delivered to all targets. such an object is called a
|
|
||||||
'delivery'. (this is not related to whether or not the target was
|
|
||||||
successful in delivering the event, just that it was proxied.)
|
|
||||||
|
|
||||||
each target type can deliver the webhook in a different way. the first
|
|
||||||
target type simply POSTs the webhook to a configured URL, doing a direct
|
|
||||||
fire-and-forget proxy. the duration, result, status code, and any other
|
|
||||||
metadata is logged in the database associated with the 'delivery' object
|
|
||||||
(which is the pairing of that event with that target).
|
|
||||||
|
|
||||||
the second target type is a 'retry' target. this target will retry the
|
|
||||||
delivery of the event to the target until it is successful, or until a
|
|
||||||
per-target maximum retry count is reached. the retry target will
|
|
||||||
exponentially back off the retry attempts, starting at 1 second and
|
|
||||||
increasing 10x each time.
|
|
||||||
|
|
||||||
the third target type is a database target. this target will store the
|
|
||||||
event in the database, but will not attempt to deliver it to any endpoint.
|
|
||||||
|
|
||||||
the fourth target type is a log target. this target will log the event
|
|
||||||
to the console, but will not attempt to deliver it to any endpoint.
|
|
||||||
|
|
||||||
## features
|
|
||||||
|
|
||||||
* store and forward (retries can be set to unlimited)
|
|
||||||
* multiple webhook endpoints per processor
|
|
||||||
* metrics availability in prometheus format for observability
|
|
||||||
* web interface for managing processors, webhooks, and targets
|
|
||||||
* web interface for viewing deliveries, events, and delivery results
|
|
||||||
* web interface for manually redelivering events to targets
|
|
||||||
|
|
||||||
## database tables
|
|
||||||
|
|
||||||
* `users` - these are the users of the webhooker service. each user has a
|
|
||||||
unique username and password, and can have multiple API keys.
|
|
||||||
* `processors` - many to one with users.
|
|
||||||
* each processor has a retention period, which is the number of days
|
|
||||||
that events will be retained in the database before being deleted.
|
|
||||||
* `webhooks` - these are the URLs that feed into processors. many to one.
|
|
||||||
* `targets` - these are the delivery targets for each processor. many to one with processors.
|
|
||||||
* each target has a type, which can be one of the following:
|
|
||||||
* `http` - this target will deliver the event to a configured URL via HTTP POST.
|
|
||||||
* `retry` - this target will retry the delivery of the event until it is successful or a maximum retry count is reached.
|
|
||||||
* each 'retry' target can have a maximum queue size, which is the maximum number of events that can be queued for retry delivery. when the queue size is reached, the oldest events will be purged from the queue.
|
|
||||||
* `database` - this target will store the event in a database, but will not attempt to deliver it to any endpoint.
|
|
||||||
* `log` - this target will log the event to the console, but will not attempt to deliver it to any endpoint.
|
|
||||||
* `deliveries` - these are the delivery attempts for each target. many to one with targets.
|
|
||||||
* `events` - these are the events that are delivered to targets. one to 0 or more with deliveries, based on targets
|
|
||||||
* `delivery_results` - these are the results of each delivery attempt. many to one with deliveries, but 1-to-1 in the usual case.
|
|
||||||
|
|
||||||
# Web Application
|
|
||||||
|
|
||||||
webhooker is a go application that provides a standard non-SPA web interface
|
|
||||||
for managing one's processors, webhooks, targets, and API keys. it allows
|
|
||||||
you to view your deliveries, events, and delivery results, and to optionally
|
|
||||||
manually redeliver events to targets.
|
|
||||||
|
|
||||||
the web application is implemented primarily without javascript, using
|
|
||||||
bootstrap for styling and layout. it uses go templates for rendering HTML,
|
|
||||||
and those templates are embedded in the binary using go:embed.
|
|
||||||
|
|
||||||
# architecture
|
|
||||||
|
|
||||||
webhooker uses Uber's fx dependency injection library for managing
|
|
||||||
application lifecycle. it uses log/slog for structured logging. it uses
|
|
||||||
gorm for database access, and initially uses sqlite for the database.
|
|
||||||
|
|
||||||
# Development Policies and Practices
|
|
||||||
|
|
||||||
## Code Structure and Organization
|
|
||||||
|
|
||||||
### Package Layout
|
|
||||||
- All application code lives under `internal/` to prevent external imports
|
|
||||||
- Main entry point is in `cmd/webhooker/`
|
|
||||||
- Package structure:
|
|
||||||
- `internal/config` - Configuration management using Viper
|
|
||||||
- `internal/database` - Database connections and migrations
|
|
||||||
- `internal/globals` - Global application metadata (version, build info)
|
|
||||||
- `internal/handlers` - HTTP handlers using closure pattern
|
|
||||||
- `internal/healthcheck` - Health check endpoint logic
|
|
||||||
- `internal/logger` - Structured logging setup
|
|
||||||
- `internal/middleware` - HTTP middleware (auth, CORS, logging, metrics)
|
|
||||||
- `internal/server` - HTTP server setup and routing
|
|
||||||
- `static/` - Static assets (CSS, JS)
|
|
||||||
- `templates/` - Go HTML templates
|
|
||||||
|
|
||||||
### Dependency Injection
|
|
||||||
- Use Uber's fx for dependency injection and lifecycle management
|
|
||||||
- All components should be provided via fx.Provide in main.go
|
|
||||||
- Use parameter structs with fx.In for dependency injection
|
|
||||||
- Follow fx lifecycle hooks (OnStart, OnStop) for initialization
|
|
||||||
|
|
||||||
### HTTP Routing and Handlers
|
|
||||||
- Use chi router for HTTP routing
|
|
||||||
- Handlers are methods on a Handlers struct that return http.HandlerFunc
|
|
||||||
- Use closure pattern for handlers:
|
|
||||||
```go
|
|
||||||
func (h *Handlers) HandleIndex() http.HandlerFunc {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// handler logic here
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- Group related routes using chi's Route method
|
|
||||||
- Apply middleware in correct order: recovery, request ID, logging, metrics, CORS, timeout
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
- Use Viper for configuration management
|
|
||||||
- Support environment variables with automatic binding
|
|
||||||
- Use `.env` files for local development (via godotenv/autoload)
|
|
||||||
- Set sensible defaults for all configuration values
|
|
||||||
- Configuration precedence: env vars > config file > defaults
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
- Use zerolog for structured JSON logging
|
|
||||||
- Always log in UTC
|
|
||||||
- Use console writer for TTY, JSON for production
|
|
||||||
- Include request ID in all request-scoped logs
|
|
||||||
- Log levels: Debug (development only), Info, Warn, Error, Fatal
|
|
||||||
- Include relevant context in all log messages
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
- Use Sentry for error reporting in production (if SENTRY_DSN is set)
|
|
||||||
- Implement panic recovery middleware
|
|
||||||
- Return appropriate HTTP status codes
|
|
||||||
- Log all errors with context
|
|
||||||
|
|
||||||
### Database
|
|
||||||
- Use GORM for database operations
|
|
||||||
- Support SQLite for development, PostgreSQL for production
|
|
||||||
- Run migrations on startup
|
|
||||||
- Use UTC for all timestamps
|
|
||||||
- Implement proper connection pooling
|
|
||||||
|
|
||||||
### Testing and Quality
|
|
||||||
- Write tests for all new functionality
|
|
||||||
- Use table-driven tests where appropriate
|
|
||||||
- Mock external dependencies in tests
|
|
||||||
- Achieve reasonable test coverage (not 100% required)
|
|
||||||
- Use `testify` for assertions
|
|
||||||
|
|
||||||
### Development Workflow
|
|
||||||
- Use Makefile with standard targets:
|
|
||||||
- `make test` - Run all tests with `go test -v ./...`
|
|
||||||
- `make fmt` - Format code with `go fmt`
|
|
||||||
- `make lint` - Run golangci-lint
|
|
||||||
- `make build` - Build the binary
|
|
||||||
- Default target is `test`
|
|
||||||
- Pre-commit checks: `make test && make fmt && make lint`
|
|
||||||
- Use golangci-lint for linting with appropriate configuration
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
- Follow standard Go idioms and effective Go guidelines
|
|
||||||
- Use constants for repeated values (no magic numbers/strings)
|
|
||||||
- Define constants at package level or top of file
|
|
||||||
- Keep functions small and focused
|
|
||||||
- Use meaningful variable names
|
|
||||||
- Comment exported functions and types
|
|
||||||
- Use structured types for request/response near usage
|
|
||||||
|
|
||||||
### Security
|
|
||||||
- Implement proper authentication for admin endpoints
|
|
||||||
- Use basic auth for metrics endpoint
|
|
||||||
- Sanitize all user input
|
|
||||||
- Use prepared statements for database queries
|
|
||||||
- Don't log sensitive information (passwords, API keys)
|
|
||||||
|
|
||||||
### Observability
|
|
||||||
- Expose Prometheus metrics on `/metrics` (behind auth)
|
|
||||||
- Include standard HTTP metrics (request count, duration, size)
|
|
||||||
- Add custom metrics for business logic
|
|
||||||
- Implement health check endpoint at `/.well-known/healthcheck.json`
|
|
||||||
- Include version info in health check response
|
|
||||||
|
|
||||||
### Static Assets
|
|
||||||
- Embed static files and templates in binary using go:embed
|
|
||||||
- Serve static files from `/s/` path
|
|
||||||
- Use Bootstrap for UI without heavy JavaScript
|
|
||||||
- Prefer server-side rendering over SPA
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
- Multi-stage Dockerfile for minimal image size
|
|
||||||
- Run as non-root user in container
|
|
||||||
- Include health check in Dockerfile
|
|
||||||
- Use Alpine Linux for smaller images
|
|
||||||
|
|
||||||
## Current Implementation Status
|
|
||||||
|
|
||||||
### What's Working
|
|
||||||
|
|
||||||
1. **Core Infrastructure**
|
|
||||||
- Dependency injection using Uber's fx
|
|
||||||
- Structured logging with Go's standard log/slog (replaced zerolog)
|
|
||||||
- Configuration management with Viper
|
|
||||||
- Environment variable support with godotenv
|
|
||||||
- Pure Go SQLite database driver (modernc.org/sqlite) - no CGO required
|
|
||||||
|
|
||||||
2. **Web Server**
|
|
||||||
- HTTP server with chi router
|
|
||||||
- Middleware stack (recovery, request ID, logging, CORS, metrics, timeout)
|
|
||||||
- Static file serving from embedded filesystem
|
|
||||||
- JSON API endpoints
|
|
||||||
- Health check endpoint at `/.well-known/healthcheck.json`
|
|
||||||
- Metrics endpoint at `/metrics` (with basic auth)
|
|
||||||
|
|
||||||
3. **Database Models**
|
|
||||||
- Full GORM models for all entities (users, processors, webhooks, targets, deliveries, events, delivery_results)
|
|
||||||
- Argon2id password hashing with secure defaults
|
|
||||||
- Auto-creation of admin user on first startup (password logged once)
|
|
||||||
|
|
||||||
4. **Testing**
|
|
||||||
- Unit tests for globals, logger, and database packages
|
|
||||||
- Database connection test that verifies SQLite works without CGO
|
|
||||||
- All tests pass
|
|
||||||
|
|
||||||
5. **Development Tools**
|
|
||||||
- Makefile with test, fmt, lint, build targets
|
|
||||||
- golangci-lint configuration (some deprecated linters need updating)
|
|
||||||
- Docker multi-stage build (updated to support CGO if needed)
|
|
||||||
- gitignore file
|
|
||||||
|
|
||||||
### API Endpoints
|
|
||||||
|
|
||||||
- `GET /` - Returns JSON welcome message
|
|
||||||
- `GET /.well-known/healthcheck.json` - Health check with uptime, version info
|
|
||||||
- `GET /s/*` - Static file serving (CSS, JS, Bootstrap)
|
|
||||||
- `GET /metrics` - Prometheus metrics (requires auth)
|
|
||||||
- `GET /api/v1/*` - API route group (not implemented yet)
|
|
||||||
|
|
||||||
### Next Steps
|
|
||||||
|
|
||||||
1. **Authentication**
|
|
||||||
- Implement user login/logout
|
|
||||||
- API key generation and validation
|
|
||||||
- Session management
|
|
||||||
- Protected routes
|
|
||||||
|
|
||||||
2. **Core Features**
|
|
||||||
- Webhook receiver endpoints
|
|
||||||
- Event processing logic
|
|
||||||
- Target delivery system (HTTP, retry, database, log)
|
|
||||||
- Web UI templates for management
|
|
||||||
|
|
||||||
3. **API Implementation**
|
|
||||||
- CRUD endpoints for processors, webhooks, targets
|
|
||||||
- Event viewing and filtering
|
|
||||||
- Delivery status and retry management
|
|
||||||
|
|
||||||
4. **Cleanup**
|
|
||||||
- Update golangci-lint config to remove deprecated linters
|
|
||||||
- Add more comprehensive tests
|
|
||||||
- Implement proper error handling
|
|
||||||
|
|
||||||
### Running the Application
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Development
|
# Clone the repo
|
||||||
|
git clone https://git.eeqj.de/sneak/webhooker.git
|
||||||
|
cd webhooker
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
make deps
|
||||||
|
|
||||||
|
# Copy example config
|
||||||
|
cp configs/config.yaml.example config.yaml
|
||||||
|
|
||||||
|
# Run in development mode
|
||||||
make dev
|
make dev
|
||||||
|
|
||||||
# Build and run
|
# Run all checks (format, lint, test, build)
|
||||||
make build
|
make check
|
||||||
./bin/webhooker
|
|
||||||
|
|
||||||
# Run tests
|
# Build Docker image
|
||||||
make test
|
make docker
|
||||||
|
|
||||||
# Docker
|
|
||||||
make docker-build
|
|
||||||
make docker-run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Environment Variables
|
### Environment Variables
|
||||||
|
|
||||||
- `DEBUG` - Enable debug logging
|
- `WEBHOOKER_ENVIRONMENT` — `dev` or `prod` (default: `dev`)
|
||||||
- `DEVELOPMENT_MODE` - Enable development features
|
- `DEBUG` — Enable debug logging
|
||||||
- `PORT` - Server port (default: 8080)
|
- `PORT` — Server port (default: `8080`)
|
||||||
- `DBURL` - Database connection string
|
- `DBURL` — Database connection string
|
||||||
- `METRICS_USERNAME` - Username for metrics endpoint
|
- `SESSION_KEY` — Base64-encoded 32-byte session key (required in prod)
|
||||||
- `METRICS_PASSWORD` - Password for metrics endpoint
|
- `METRICS_USERNAME` — Username for metrics endpoint
|
||||||
- `SENTRY_DSN` - Sentry error reporting (optional)
|
- `METRICS_PASSWORD` — Password for metrics endpoint
|
||||||
|
- `SENTRY_DSN` — Sentry error reporting (optional)
|
||||||
|
|
||||||
# purpose
|
## Rationale
|
||||||
|
|
||||||
* prometheus/grafana metric analysis of webhook frequency, size, and
|
Webhook integrations between services are fragile: the receiving service
|
||||||
|
must be up when the webhook fires, there is no built-in retry for most
|
||||||
|
webhook senders, and there is no visibility into what was sent or when.
|
||||||
|
webhooker solves this by acting as a reliable intermediary that receives
|
||||||
|
webhooks, stores them, and delivers them to configured targets — with
|
||||||
|
optional retries, logging, and Prometheus metrics for observability.
|
||||||
|
|
||||||
|
Use cases include:
|
||||||
|
|
||||||
|
- Store-and-forward with unlimited retries for unreliable receivers
|
||||||
|
- Prometheus/Grafana metric analysis of webhook frequency, size, and
|
||||||
handler performance
|
handler performance
|
||||||
* introspection of webhook data
|
- Introspection and debugging of webhook payloads
|
||||||
* debugging of webhook delivery issues
|
- Redelivery of webhook events for application testing
|
||||||
* redelivery of webhook events for application testing and development
|
- Fan-out delivery of webhooks to multiple targets
|
||||||
* storage of webhook events for later analysis
|
- HA ingestion endpoint for delivery to less reliable systems
|
||||||
* webhook delivery to multiple targets
|
|
||||||
* webhook delivery with retries for not-always-on applications
|
|
||||||
* can serve as an HA endpoint for ingestion to deliver events to less
|
|
||||||
reliable systems
|
|
||||||
|
|
||||||
# future plans
|
## Design
|
||||||
|
|
||||||
* email source type
|
### Architecture
|
||||||
* email delivery target
|
|
||||||
* sns delivery target
|
|
||||||
* s3 delivery target
|
|
||||||
* data transformations (e.g. to convert event notification data to slack
|
|
||||||
messages)
|
|
||||||
* slack delivery target
|
|
||||||
* slack source type
|
|
||||||
* text file or jsonl delivery target, with periodic uploading to s3
|
|
||||||
|
|
||||||
|
webhooker uses Uber's fx dependency injection library for managing
|
||||||
|
application lifecycle. It uses `log/slog` for structured logging, GORM
|
||||||
|
for database access, and SQLite (via `modernc.org/sqlite`, pure Go, no
|
||||||
|
CGO) for storage. HTTP routing uses chi.
|
||||||
|
|
||||||
|
### Package Layout
|
||||||
|
|
||||||
|
All application code lives under `internal/` to prevent external imports.
|
||||||
|
The main entry point is `cmd/webhooker/main.go`.
|
||||||
|
|
||||||
|
- `internal/config` — Configuration management via `pkg/config` (Viper-based)
|
||||||
|
- `internal/database` — GORM database connection, migrations, and models
|
||||||
|
- `internal/globals` — Global application metadata (version, build info)
|
||||||
|
- `internal/handlers` — HTTP handlers using the closure pattern
|
||||||
|
- `internal/healthcheck` — Health check endpoint logic
|
||||||
|
- `internal/logger` — Structured logging setup (`log/slog`)
|
||||||
|
- `internal/middleware` — HTTP middleware (auth, CORS, logging, metrics)
|
||||||
|
- `internal/server` — HTTP server setup and routing
|
||||||
|
- `internal/session` — Session management (gorilla/sessions)
|
||||||
|
- `pkg/config` — Reusable multi-environment configuration library
|
||||||
|
- `static/` — Embedded static assets (CSS, JS)
|
||||||
|
- `templates/` — Go HTML templates
|
||||||
|
|
||||||
|
### Data Model
|
||||||
|
|
||||||
|
- **Users** — Service users with username/password (Argon2id hashing)
|
||||||
|
- **Processors** — Webhook processing units, many-to-one with users
|
||||||
|
- **Webhooks** — Inbound URL endpoints feeding into processors
|
||||||
|
- **Targets** — Delivery destinations per processor (HTTP, retry, database, log)
|
||||||
|
- **Events** — Captured webhook payloads
|
||||||
|
- **Deliveries** — Pairing of events with targets
|
||||||
|
- **Delivery Results** — Outcome of each delivery attempt
|
||||||
|
- **API Keys** — Programmatic access credentials per user
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
- `GET /` — Web UI index page
|
||||||
|
- `GET /.well-known/healthcheck.json` — Health check with uptime, version
|
||||||
|
- `GET /s/*` — Static file serving (CSS, JS)
|
||||||
|
- `GET /metrics` — Prometheus metrics (requires basic auth)
|
||||||
|
- `POST /webhook/{uuid}` — Webhook receiver endpoint
|
||||||
|
- `/pages/login`, `/pages/logout` — Authentication
|
||||||
|
- `/user/{username}` — User profile
|
||||||
|
- `/sources/*` — Webhook source management
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
### Phase 1: Security & Infrastructure
|
||||||
|
- [ ] Security headers (HSTS, CSP, X-Frame-Options)
|
||||||
|
- [ ] Rate limiting middleware
|
||||||
|
- [ ] CSRF protection for forms
|
||||||
|
- [ ] Request ID tracking through entire lifecycle
|
||||||
|
|
||||||
|
### Phase 2: Authentication & Authorization
|
||||||
|
- [ ] Authentication middleware for protected routes
|
||||||
|
- [ ] Session expiration and "remember me"
|
||||||
|
- [ ] Password reset flow
|
||||||
|
- [ ] API key authentication for programmatic access
|
||||||
|
|
||||||
|
### Phase 3: Core Webhook Features
|
||||||
|
- [ ] Webhook reception and event storage at `/webhook/{uuid}`
|
||||||
|
- [ ] Event processing and target delivery engine
|
||||||
|
- [ ] HTTP target type (fire-and-forget POST)
|
||||||
|
- [ ] Retry target type (exponential backoff)
|
||||||
|
- [ ] Database target type (store only)
|
||||||
|
- [ ] Log target type (console output)
|
||||||
|
- [ ] Webhook signature verification (GitHub, Stripe formats)
|
||||||
|
|
||||||
|
### Phase 4: Web UI
|
||||||
|
- [ ] Webhook source 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)
|
||||||
|
|
||||||
|
### Phase 5: API
|
||||||
|
- [ ] RESTful CRUD for processors, webhooks, targets
|
||||||
|
- [ ] Event viewing and filtering endpoints
|
||||||
|
- [ ] API documentation (OpenAPI)
|
||||||
|
|
||||||
|
### Future
|
||||||
|
- [ ] Email source and delivery target types
|
||||||
|
- [ ] SNS, S3, Slack delivery targets
|
||||||
|
- [ ] Data transformations (e.g. webhook-to-Slack message)
|
||||||
|
- [ ] JSONL file delivery with periodic S3 upload
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Pending — to be determined by the author (MIT, GPL, or WTFPL).
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
[@sneak](https://sneak.berlin)
|
||||||
|
|||||||
188
REPO_POLICIES.md
Normal file
188
REPO_POLICIES.md
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
---
|
||||||
|
title: Repository Policies
|
||||||
|
last_modified: 2026-02-22
|
||||||
|
---
|
||||||
|
|
||||||
|
This document covers repository structure, tooling, and workflow standards. Code
|
||||||
|
style conventions are in separate documents:
|
||||||
|
|
||||||
|
- [Code Styleguide](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE.md)
|
||||||
|
(general, bash, Docker)
|
||||||
|
- [Go](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_GO.md)
|
||||||
|
- [JavaScript](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_JS.md)
|
||||||
|
- [Python](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/CODE_STYLEGUIDE_PYTHON.md)
|
||||||
|
- [Go HTTP Server Conventions](https://git.eeqj.de/sneak/prompts/raw/branch/main/prompts/GO_HTTP_SERVER_CONVENTIONS.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- Cross-project documentation (such as this file) must include
|
||||||
|
`last_modified: YYYY-MM-DD` in the YAML front matter so it can be kept in sync
|
||||||
|
with the authoritative source as policies evolve.
|
||||||
|
|
||||||
|
- **ALL external references must be pinned by cryptographic hash.** This
|
||||||
|
includes Docker base images, Go modules, npm packages, GitHub Actions, and
|
||||||
|
anything else fetched from a remote source. Version tags (`@v4`, `@latest`,
|
||||||
|
`:3.21`, etc.) are server-mutable and therefore remote code execution
|
||||||
|
vulnerabilities. The ONLY acceptable way to reference an external dependency
|
||||||
|
is by its content hash (Docker `@sha256:...`, Go module hash in `go.sum`, npm
|
||||||
|
integrity hash in lockfile, GitHub Actions `@<commit-sha>`). No exceptions.
|
||||||
|
This also means never `curl | bash` to install tools like pyenv, nvm, rustup,
|
||||||
|
etc. Instead, download a specific release archive from GitHub, verify its hash
|
||||||
|
(hardcoded in the Dockerfile or script), and only then install. Unverified
|
||||||
|
install scripts are arbitrary remote code execution. This is the single most
|
||||||
|
important rule in this document. Double-check every external reference in
|
||||||
|
every file before committing. There are zero exceptions to this rule.
|
||||||
|
|
||||||
|
- Every repo with software must have a root `Makefile` with these targets:
|
||||||
|
`make test`, `make lint`, `make fmt` (writes), `make fmt-check` (read-only),
|
||||||
|
`make check` (prereqs: `test`, `lint`, `fmt-check`), `make docker`, and
|
||||||
|
`make hooks` (installs pre-commit hook). A model Makefile is at
|
||||||
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/Makefile`.
|
||||||
|
|
||||||
|
- Always use Makefile targets (`make fmt`, `make test`, `make lint`, etc.)
|
||||||
|
instead of invoking the underlying tools directly. The Makefile is the single
|
||||||
|
source of truth for how these operations are run.
|
||||||
|
|
||||||
|
- The Makefile is authoritative documentation for how the repo is used. Beyond
|
||||||
|
the required targets above, it should have targets for every common operation:
|
||||||
|
running a local development server (`make run`, `make dev`), re-initializing
|
||||||
|
or migrating the database (`make db-reset`, `make migrate`), building
|
||||||
|
artifacts (`make build`), generating code, seeding data, or anything else a
|
||||||
|
developer would do regularly. If someone checks out the repo and types
|
||||||
|
`make<tab>`, they should see every meaningful operation available. A new
|
||||||
|
contributor should be able to understand the entire development workflow by
|
||||||
|
reading the Makefile.
|
||||||
|
|
||||||
|
- Every repo should have a `Dockerfile`. All Dockerfiles must run `make check`
|
||||||
|
as a build step so the build fails if the branch is not green. For non-server
|
||||||
|
repos, the Dockerfile should bring up a development environment and run
|
||||||
|
`make check`. For server repos, `make check` should run as an early build
|
||||||
|
stage before the final image is assembled.
|
||||||
|
|
||||||
|
- Every repo should have a Gitea Actions workflow (`.gitea/workflows/`) that
|
||||||
|
runs `docker build .` on push. Since the Dockerfile already runs `make check`,
|
||||||
|
a successful build implies all checks pass.
|
||||||
|
|
||||||
|
- Use platform-standard formatters: `black` for Python, `prettier` for
|
||||||
|
JS/CSS/Markdown/HTML, `go fmt` for Go. Always use default configuration with
|
||||||
|
two exceptions: four-space indents (except Go), and `proseWrap: always` for
|
||||||
|
Markdown (hard-wrap at 80 columns). Documentation and writing repos (Markdown,
|
||||||
|
HTML, CSS) should also have `.prettierrc` and `.prettierignore`.
|
||||||
|
|
||||||
|
- Pre-commit hook: `make check` if local testing is possible, otherwise
|
||||||
|
`make lint && make fmt-check`. The Makefile should provide a `make hooks`
|
||||||
|
target to install the pre-commit hook.
|
||||||
|
|
||||||
|
- All repos with software must have tests that run via the platform-standard
|
||||||
|
test framework (`go test`, `pytest`, `jest`/`vitest`, etc.). If no meaningful
|
||||||
|
tests exist yet, add the most minimal test possible — e.g. importing the
|
||||||
|
module under test to verify it compiles/parses. There is no excuse for
|
||||||
|
`make test` to be a no-op.
|
||||||
|
|
||||||
|
- `make test` must complete in under 20 seconds. Add a 30-second timeout in the
|
||||||
|
Makefile.
|
||||||
|
|
||||||
|
- Docker builds must complete in under 5 minutes.
|
||||||
|
|
||||||
|
- `make check` must not modify any files in the repo. Tests may use temporary
|
||||||
|
directories.
|
||||||
|
|
||||||
|
- `main` must always pass `make check`, no exceptions.
|
||||||
|
|
||||||
|
- Never commit secrets. `.env` files, credentials, API keys, and private keys
|
||||||
|
must be in `.gitignore`. No exceptions.
|
||||||
|
|
||||||
|
- `.gitignore` should be comprehensive from the start: OS files (`.DS_Store`),
|
||||||
|
editor files (`.swp`, `*~`), language build artifacts, and `node_modules/`.
|
||||||
|
Fetch the standard `.gitignore` from
|
||||||
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.gitignore` when setting up
|
||||||
|
a new repo.
|
||||||
|
|
||||||
|
- Never use `git add -A` or `git add .`. Always stage files explicitly by name.
|
||||||
|
|
||||||
|
- Never force-push to `main`.
|
||||||
|
|
||||||
|
- Make all changes on a feature branch. You can do whatever you want on a
|
||||||
|
feature branch.
|
||||||
|
|
||||||
|
- `.golangci.yml` is standardized and must _NEVER_ be modified by an agent, only
|
||||||
|
manually by the user. Fetch from
|
||||||
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/.golangci.yml`.
|
||||||
|
|
||||||
|
- When pinning images or packages by hash, add a comment above the reference
|
||||||
|
with the version and date (YYYY-MM-DD).
|
||||||
|
|
||||||
|
- Use `yarn`, not `npm`.
|
||||||
|
|
||||||
|
- Write all dates as YYYY-MM-DD (ISO 8601).
|
||||||
|
|
||||||
|
- Simple projects should be configured with environment variables.
|
||||||
|
|
||||||
|
- Dockerized web services listen on port 8080 by default, overridable with
|
||||||
|
`PORT`.
|
||||||
|
|
||||||
|
- `README.md` is the primary documentation. Required sections:
|
||||||
|
- **Description**: First line must include the project name, purpose,
|
||||||
|
category (web server, SPA, CLI tool, etc.), license, and author. Example:
|
||||||
|
"µPaaS is an MIT-licensed Go web application by @sneak that receives
|
||||||
|
git-frontend webhooks and deploys applications via Docker in realtime."
|
||||||
|
- **Getting Started**: Copy-pasteable install/usage code block.
|
||||||
|
- **Rationale**: Why does this exist?
|
||||||
|
- **Design**: How is the program structured?
|
||||||
|
- **TODO**: Update meticulously, even between commits. When planning, put
|
||||||
|
the todo list in the README so a new agent can pick up where the last one
|
||||||
|
left off.
|
||||||
|
- **License**: MIT, GPL, or WTFPL. Ask the user for new projects. Include a
|
||||||
|
`LICENSE` file in the repo root and a License section in the README.
|
||||||
|
- **Author**: [@sneak](https://sneak.berlin).
|
||||||
|
|
||||||
|
- First commit of a new repo should contain only `README.md`.
|
||||||
|
|
||||||
|
- Go module root: `sneak.berlin/go/<name>`. Always run `go mod tidy` before
|
||||||
|
committing.
|
||||||
|
|
||||||
|
- Use SemVer.
|
||||||
|
|
||||||
|
- Database migrations live in `internal/db/migrations/` and must be embedded in
|
||||||
|
the binary.
|
||||||
|
- `000_migration.sql` — contains ONLY the creation of the migrations tracking
|
||||||
|
table itself. Nothing else.
|
||||||
|
- `001_schema.sql` — the full application schema.
|
||||||
|
- **Pre-1.0.0:** never add additional migration files (002, 003, etc.). There
|
||||||
|
is no installed base to migrate. Edit `001_schema.sql` directly.
|
||||||
|
- **Post-1.0.0:** add new numbered migration files for each schema change.
|
||||||
|
Never edit existing migrations after release.
|
||||||
|
|
||||||
|
- All repos should have an `.editorconfig` enforcing the project's indentation
|
||||||
|
settings.
|
||||||
|
|
||||||
|
- Avoid putting files in the repo root unless necessary. Root should contain
|
||||||
|
only project-level config files (`README.md`, `Makefile`, `Dockerfile`,
|
||||||
|
`LICENSE`, `.gitignore`, `.editorconfig`, `REPO_POLICIES.md`, and
|
||||||
|
language-specific config). Everything else goes in a subdirectory. Canonical
|
||||||
|
subdirectory names:
|
||||||
|
- `bin/` — executable scripts and tools
|
||||||
|
- `cmd/` — Go command entrypoints
|
||||||
|
- `configs/` — configuration templates and examples
|
||||||
|
- `deploy/` — deployment manifests (k8s, compose, terraform)
|
||||||
|
- `docs/` — documentation and markdown (README.md stays in root)
|
||||||
|
- `internal/` — Go internal packages
|
||||||
|
- `internal/db/migrations/` — database migrations
|
||||||
|
- `pkg/` — Go library packages
|
||||||
|
- `share/` — systemd units, data files
|
||||||
|
- `static/` — static assets (images, fonts, etc.)
|
||||||
|
- `web/` — web frontend source
|
||||||
|
|
||||||
|
- When setting up a new repo, files from the `prompts` repo may be used as
|
||||||
|
templates. Fetch them from
|
||||||
|
`https://git.eeqj.de/sneak/prompts/raw/branch/main/<path>`.
|
||||||
|
|
||||||
|
- New repos must contain at minimum:
|
||||||
|
- `README.md`, `.git`, `.gitignore`, `.editorconfig`
|
||||||
|
- `LICENSE`, `REPO_POLICIES.md` (copy from the `prompts` repo)
|
||||||
|
- `Makefile`
|
||||||
|
- `Dockerfile`, `.dockerignore`
|
||||||
|
- `.gitea/workflows/check.yml`
|
||||||
|
- Go: `go.mod`, `go.sum`, `.golangci.yml`
|
||||||
|
- JS: `package.json`, `yarn.lock`, `.prettierrc`, `.prettierignore`
|
||||||
|
- Python: `pyproject.toml`
|
||||||
102
TODO.md
102
TODO.md
@@ -1,102 +0,0 @@
|
|||||||
# Webhooker TODO List
|
|
||||||
|
|
||||||
## Phase 1: Security & Infrastructure Hardening
|
|
||||||
- [ ] Implement proper security headers (HSTS, CSP, X-Frame-Options, etc.)
|
|
||||||
- [ ] Add request timeouts and context handling
|
|
||||||
- [ ] Set maximum request/response body sizes
|
|
||||||
- [ ] Implement rate limiting middleware
|
|
||||||
- [ ] Add CSRF protection for forms
|
|
||||||
- [ ] Set up proper CORS handling
|
|
||||||
- [ ] Implement request ID tracking through entire request lifecycle
|
|
||||||
- [ ] Add panic recovery with proper error reporting
|
|
||||||
|
|
||||||
## Phase 2: Authentication & Authorization
|
|
||||||
- [ ] Create authentication middleware that checks session
|
|
||||||
- [ ] Implement proper session expiration
|
|
||||||
- [ ] Add "Remember me" functionality
|
|
||||||
- [ ] Implement password reset flow
|
|
||||||
- [ ] Add user registration (if needed)
|
|
||||||
- [ ] Create authorization middleware for protected routes
|
|
||||||
- [ ] Add API key authentication for programmatic access
|
|
||||||
|
|
||||||
## Phase 3: Database Models & Migrations
|
|
||||||
- [ ] Create webhook source model (id, user_id, name, target_url, secret, created_at, etc.)
|
|
||||||
- [ ] Create webhook request log model (id, source_id, request_headers, request_body, response_status, etc.)
|
|
||||||
- [ ] Create webhook retry model for failed deliveries
|
|
||||||
- [ ] Add database indexes for performance
|
|
||||||
- [ ] Create migration system for schema updates
|
|
||||||
|
|
||||||
## Phase 4: Webhook Source Management UI
|
|
||||||
- [ ] Implement webhook source list page (/sources)
|
|
||||||
- [ ] Create webhook source creation form (/sources/new)
|
|
||||||
- [ ] Build webhook source detail page (/source/{id})
|
|
||||||
- [ ] Add webhook source edit functionality (/source/{id}/edit)
|
|
||||||
- [ ] Implement webhook source deletion with confirmation
|
|
||||||
- [ ] Add webhook URL generation and display
|
|
||||||
- [ ] Create secret key generation and management
|
|
||||||
- [ ] Add webhook testing functionality
|
|
||||||
|
|
||||||
## Phase 5: Webhook Processing Engine
|
|
||||||
- [ ] Implement actual webhook reception at /webhook/{uuid}
|
|
||||||
- [ ] Validate incoming webhook requests (headers, body size, etc.)
|
|
||||||
- [ ] Create webhook forwarding logic to target URLs
|
|
||||||
- [ ] Implement request/response logging
|
|
||||||
- [ ] Add webhook signature verification (GitHub, Stripe, etc. formats)
|
|
||||||
- [ ] Create webhook transformation capabilities (headers, body)
|
|
||||||
- [ ] Implement timeout handling for outbound requests
|
|
||||||
- [ ] Add retry logic with exponential backoff
|
|
||||||
|
|
||||||
## Phase 6: Webhook Logs & Analytics
|
|
||||||
- [ ] Create webhook request log viewer (/source/{id}/logs)
|
|
||||||
- [ ] Add filtering and search capabilities for logs
|
|
||||||
- [ ] Implement request/response body viewer
|
|
||||||
- [ ] Create analytics dashboard (success rates, response times)
|
|
||||||
- [ ] Add webhook health monitoring
|
|
||||||
- [ ] Implement alerting for failed webhooks
|
|
||||||
- [ ] Create log retention policies
|
|
||||||
|
|
||||||
## Phase 7: Advanced Features
|
|
||||||
- [ ] Add webhook request replay functionality
|
|
||||||
- [ ] Implement webhook request batching
|
|
||||||
- [ ] Create webhook request queuing system
|
|
||||||
- [ ] Add support for multiple target URLs per source
|
|
||||||
- [ ] Implement conditional forwarding based on payload
|
|
||||||
- [ ] Add webhook transformation templates
|
|
||||||
- [ ] Create webhook debugging tools
|
|
||||||
- [ ] Implement webhook scheduling/delayed delivery
|
|
||||||
|
|
||||||
## Phase 8: API Development
|
|
||||||
- [ ] Create RESTful API for webhook source management
|
|
||||||
- [ ] Implement API authentication and rate limiting
|
|
||||||
- [ ] Add API documentation (OpenAPI/Swagger)
|
|
||||||
- [ ] Create API client libraries
|
|
||||||
- [ ] Implement webhooks-as-a-service API
|
|
||||||
|
|
||||||
## Phase 9: Performance & Scalability
|
|
||||||
- [ ] Implement caching layer (Redis)
|
|
||||||
- [ ] Add background job processing (for retries, etc.)
|
|
||||||
- [ ] Create horizontal scaling capabilities
|
|
||||||
- [ ] Implement webhook delivery parallelization
|
|
||||||
- [ ] Add metrics collection (Prometheus)
|
|
||||||
- [ ] Create performance monitoring dashboard
|
|
||||||
|
|
||||||
## Phase 10: Operations & Maintenance
|
|
||||||
- [ ] Add comprehensive logging throughout application
|
|
||||||
- [ ] Create admin dashboard for user management
|
|
||||||
- [ ] Implement backup and restore procedures
|
|
||||||
- [ ] Add system health checks and monitoring
|
|
||||||
- [ ] Create deployment automation (Docker, K8s)
|
|
||||||
- [ ] Implement zero-downtime deployments
|
|
||||||
- [ ] Add feature flags for gradual rollouts
|
|
||||||
|
|
||||||
## Nice-to-Have Features
|
|
||||||
- [ ] Webhook marketplace/templates
|
|
||||||
- [ ] Team collaboration features
|
|
||||||
- [ ] Webhook versioning
|
|
||||||
- [ ] A/B testing for webhooks
|
|
||||||
- [ ] Webhook analytics export
|
|
||||||
- [ ] Mobile app for monitoring
|
|
||||||
- [ ] Slack/Discord/Email notifications
|
|
||||||
- [ ] Webhook documentation generator
|
|
||||||
- [ ] GraphQL subscription support
|
|
||||||
- [ ] WebSocket support for real-time updates
|
|
||||||
43
cmd/webhooker/main.go
Normal file
43
cmd/webhooker/main.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/config"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/database"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/globals"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/handlers"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/healthcheck"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/logger"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/middleware"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/server"
|
||||||
|
"git.eeqj.de/sneak/webhooker/internal/session"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build-time variables set via -ldflags.
|
||||||
|
var (
|
||||||
|
version = "dev"
|
||||||
|
appname = "webhooker"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
globals.Appname = appname
|
||||||
|
globals.Version = version
|
||||||
|
globals.Buildarch = runtime.GOARCH
|
||||||
|
|
||||||
|
fx.New(
|
||||||
|
fx.Provide(
|
||||||
|
globals.New,
|
||||||
|
logger.New,
|
||||||
|
config.New,
|
||||||
|
database.New,
|
||||||
|
healthcheck.New,
|
||||||
|
session.New,
|
||||||
|
handlers.New,
|
||||||
|
middleware.New,
|
||||||
|
server.New,
|
||||||
|
),
|
||||||
|
fx.Invoke(func(*server.Server) {}),
|
||||||
|
).Run()
|
||||||
|
}
|
||||||
17
go.mod
17
go.mod
@@ -15,7 +15,7 @@ require (
|
|||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/prometheus/client_golang v1.18.0
|
github.com/prometheus/client_golang v1.18.0
|
||||||
github.com/slok/go-http-metrics v0.11.0
|
github.com/slok/go-http-metrics v0.11.0
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/afero v1.14.0
|
||||||
github.com/stretchr/testify v1.8.4
|
github.com/stretchr/testify v1.8.4
|
||||||
go.uber.org/fx v1.20.1
|
go.uber.org/fx v1.20.1
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.38.0
|
||||||
@@ -34,42 +34,30 @@ require (
|
|||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/golang/protobuf v1.5.3 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
github.com/mattn/go-sqlite3 v1.14.17 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.45.0 // indirect
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/dig v1.17.0 // indirect
|
go.uber.org/dig v1.17.0 // indirect
|
||||||
go.uber.org/multierr v1.9.0 // indirect
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
go.uber.org/zap v1.23.0 // indirect
|
go.uber.org/zap v1.23.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
|
||||||
golang.org/x/mod v0.17.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/oauth2 v0.15.0 // indirect
|
golang.org/x/oauth2 v0.15.0 // indirect
|
||||||
@@ -85,7 +73,6 @@ require (
|
|||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f // indirect
|
||||||
google.golang.org/grpc v1.59.0 // indirect
|
google.golang.org/grpc v1.59.0 // indirect
|
||||||
google.golang.org/protobuf v1.31.0 // indirect
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
|
|||||||
31
go.sum
31
go.sum
@@ -23,6 +23,7 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
|
|||||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
@@ -33,10 +34,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
|
||||||
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
|
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
|
||||||
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||||
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
|
github.com/go-chi/chi v1.5.5 h1:vOB/HbEMt9QqBqErz07QehcOKHaWFtuj87tTDVz2qXE=
|
||||||
@@ -89,8 +86,6 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX
|
|||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
@@ -107,18 +102,12 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
|
||||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -139,22 +128,10 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
|
||||||
github.com/slok/go-http-metrics v0.11.0 h1:ABJUpekCZSkQT1wQrFvS4kGbhea/w6ndFJaWJeh3zL0=
|
github.com/slok/go-http-metrics v0.11.0 h1:ABJUpekCZSkQT1wQrFvS4kGbhea/w6ndFJaWJeh3zL0=
|
||||||
github.com/slok/go-http-metrics v0.11.0/go.mod h1:ZGKeYG1ET6TEJpQx18BqAJAvxw9jBAZXCHU7bWQqqAc=
|
github.com/slok/go-http-metrics v0.11.0/go.mod h1:ZGKeYG1ET6TEJpQx18BqAJAvxw9jBAZXCHU7bWQqqAc=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
||||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
||||||
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
|
||||||
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
|
||||||
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
@@ -166,8 +143,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
@@ -187,8 +162,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
@@ -271,8 +244,6 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
|
|||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
|
||||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
|||||||
@@ -7,10 +7,35 @@ import (
|
|||||||
"git.eeqj.de/sneak/webhooker/internal/config"
|
"git.eeqj.de/sneak/webhooker/internal/config"
|
||||||
"git.eeqj.de/sneak/webhooker/internal/globals"
|
"git.eeqj.de/sneak/webhooker/internal/globals"
|
||||||
"git.eeqj.de/sneak/webhooker/internal/logger"
|
"git.eeqj.de/sneak/webhooker/internal/logger"
|
||||||
|
pkgconfig "git.eeqj.de/sneak/webhooker/pkg/config"
|
||||||
|
"github.com/spf13/afero"
|
||||||
"go.uber.org/fx/fxtest"
|
"go.uber.org/fx/fxtest"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDatabaseConnection(t *testing.T) {
|
func TestDatabaseConnection(t *testing.T) {
|
||||||
|
// Set up in-memory config so the test does not depend on config.yaml on disk
|
||||||
|
fs := afero.NewMemMapFs()
|
||||||
|
testConfigYAML := `
|
||||||
|
environments:
|
||||||
|
dev:
|
||||||
|
config:
|
||||||
|
port: 8080
|
||||||
|
debug: false
|
||||||
|
maintenanceMode: false
|
||||||
|
developmentMode: true
|
||||||
|
environment: dev
|
||||||
|
dburl: "file::memory:?cache=shared"
|
||||||
|
secrets:
|
||||||
|
sessionKey: d2ViaG9va2VyLWRldi1zZXNzaW9uLWtleS1pbnNlY3VyZSE=
|
||||||
|
sentryDSN: ""
|
||||||
|
configDefaults:
|
||||||
|
port: 8080
|
||||||
|
`
|
||||||
|
if err := afero.WriteFile(fs, "config.yaml", []byte(testConfigYAML), 0644); err != nil {
|
||||||
|
t.Fatalf("Failed to write test config: %v", err)
|
||||||
|
}
|
||||||
|
pkgconfig.SetFs(fs)
|
||||||
|
|
||||||
// Set up test dependencies
|
// Set up test dependencies
|
||||||
lc := fxtest.NewLifecycle(t)
|
lc := fxtest.NewLifecycle(t)
|
||||||
|
|
||||||
@@ -39,8 +64,8 @@ func TestDatabaseConnection(t *testing.T) {
|
|||||||
t.Fatalf("Failed to create config: %v", err)
|
t.Fatalf("Failed to create config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set test database URL
|
// Override DBURL to use a temp file-based SQLite (in-memory doesn't persist across connections)
|
||||||
c.DBURL = "file:test.db?cache=shared&mode=rwc"
|
c.DBURL = "file:" + t.TempDir() + "/test.db?cache=shared&mode=rwc"
|
||||||
|
|
||||||
// Create database
|
// Create database
|
||||||
db, err := New(lc, DatabaseParams{
|
db, err := New(lc, DatabaseParams{
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import (
|
|||||||
"embed"
|
"embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed css js vendor
|
//go:embed css js
|
||||||
var Static embed.FS
|
var Static embed.FS
|
||||||
|
|||||||
Reference in New Issue
Block a user