diff --git a/Dockerfile b/Dockerfile index ae15f7b..9d2022d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,6 +64,6 @@ USER webhooker EXPOSE 8080 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 || exit 1 CMD ["./webhooker"] diff --git a/README.md b/README.md index 758562d..7b793dc 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,39 @@ 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. +### Rate Limiting + +Global rate limiting middleware (e.g. per-IP throttling applied at the +router level) must **not** apply to webhook receiver endpoints +(`/webhook/{uuid}`). Webhook endpoints receive automated traffic from +external services at unpredictable rates, and blanket rate limits would +cause legitimate webhook deliveries to be dropped. + +Instead, each webhook endpoint has its own individually configurable rate +limit, applied within the webhook handler itself. By default, no rate +limit is applied — webhook endpoints accept traffic as fast as it arrives. +Rate limits can be configured on a per-webhook basis in the application +when needed (e.g. to protect against a misbehaving sender). + +### Database Architecture + +webhooker uses separate SQLite database files rather than a single +monolithic database: + +- **Main application database** — Stores application configuration and + all standard webapp data: users, sessions, API keys, and global + settings. +- **Per-processor databases** — Each processor (working name — a better + term is needed) gets its own dedicated SQLite database file containing: + input logs, processor logs, and all output queues for that specific + processor. + +This separation provides several benefits: processor databases can be +independently backed up, rotated, or archived; a high-volume processor +won't cause lock contention or bloat affecting the main application; and +individual processor data can be cleanly deleted when a processor is +removed. + ### Package Layout All application code lives under `internal/` to prevent external imports. @@ -98,7 +131,7 @@ The main entry point is `cmd/webhooker/main.go`. ### API Endpoints - `GET /` — Web UI index page -- `GET /.well-known/healthcheck.json` — Health check with uptime, version +- `GET /.well-known/healthcheck` — 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 diff --git a/cmd/webhooker/main.go b/cmd/webhooker/main.go index 9e09fb0..86612b7 100644 --- a/cmd/webhooker/main.go +++ b/cmd/webhooker/main.go @@ -3,16 +3,16 @@ 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" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/database" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/handlers" + "sneak.berlin/go/webhooker/internal/healthcheck" + "sneak.berlin/go/webhooker/internal/logger" + "sneak.berlin/go/webhooker/internal/middleware" + "sneak.berlin/go/webhooker/internal/server" + "sneak.berlin/go/webhooker/internal/session" ) // Build-time variables set via -ldflags. diff --git a/go.mod b/go.mod index 6cb592f..2cca99e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,10 @@ -module git.eeqj.de/sneak/webhooker +module sneak.berlin/go/webhooker go 1.23.0 toolchain go1.24.1 require ( - git.eeqj.de/sneak/webhooker/pkg/config v0.0.0-00010101000000-000000000000 github.com/99designs/basicauth-go v0.0.0-20230316000542-bf6f9cbbf0f8 github.com/getsentry/sentry-go v0.25.0 github.com/go-chi/chi v1.5.5 @@ -22,6 +21,7 @@ require ( gorm.io/driver/sqlite v1.5.4 gorm.io/gorm v1.25.5 modernc.org/sqlite v1.28.0 + sneak.berlin/go/webhooker/pkg/config v0.0.0-00010101000000-000000000000 ) require ( @@ -85,4 +85,4 @@ require ( modernc.org/token v1.0.1 // indirect ) -replace git.eeqj.de/sneak/webhooker/pkg/config => ./pkg/config +replace sneak.berlin/go/webhooker/pkg/config => ./pkg/config diff --git a/internal/config/config.go b/internal/config/config.go index 148c502..b78eae2 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,10 +5,10 @@ import ( "log/slog" "os" - "git.eeqj.de/sneak/webhooker/internal/globals" - "git.eeqj.de/sneak/webhooker/internal/logger" - pkgconfig "git.eeqj.de/sneak/webhooker/pkg/config" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/logger" + pkgconfig "sneak.berlin/go/webhooker/pkg/config" // spooky action at a distance! // this populates the environment diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 83d1abb..8435a95 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,14 +4,14 @@ import ( "os" "testing" - "git.eeqj.de/sneak/webhooker/internal/globals" - "git.eeqj.de/sneak/webhooker/internal/logger" - pkgconfig "git.eeqj.de/sneak/webhooker/pkg/config" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/logger" + pkgconfig "sneak.berlin/go/webhooker/pkg/config" ) // createTestConfig creates a test configuration file in memory diff --git a/internal/database/database.go b/internal/database/database.go index 3ad2174..90d3fe5 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -5,12 +5,12 @@ import ( "database/sql" "log/slog" - "git.eeqj.de/sneak/webhooker/internal/config" - "git.eeqj.de/sneak/webhooker/internal/logger" "go.uber.org/fx" "gorm.io/driver/sqlite" "gorm.io/gorm" _ "modernc.org/sqlite" // Pure Go SQLite driver + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/logger" ) // nolint:revive // DatabaseParams is a standard fx naming convention diff --git a/internal/database/database_test.go b/internal/database/database_test.go index f5ec38b..996fd60 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -4,12 +4,12 @@ import ( "context" "testing" - "git.eeqj.de/sneak/webhooker/internal/config" - "git.eeqj.de/sneak/webhooker/internal/globals" - "git.eeqj.de/sneak/webhooker/internal/logger" - pkgconfig "git.eeqj.de/sneak/webhooker/pkg/config" "github.com/spf13/afero" "go.uber.org/fx/fxtest" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/logger" + pkgconfig "sneak.berlin/go/webhooker/pkg/config" ) func TestDatabaseConnection(t *testing.T) { diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 776e5d1..b6809fc 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -3,7 +3,7 @@ package handlers import ( "net/http" - "git.eeqj.de/sneak/webhooker/internal/database" + "sneak.berlin/go/webhooker/internal/database" ) // HandleLoginPage returns a handler for the login page (GET) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 160ee43..0183668 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -7,12 +7,12 @@ import ( "log/slog" "net/http" - "git.eeqj.de/sneak/webhooker/internal/database" - "git.eeqj.de/sneak/webhooker/internal/globals" - "git.eeqj.de/sneak/webhooker/internal/healthcheck" - "git.eeqj.de/sneak/webhooker/internal/logger" - "git.eeqj.de/sneak/webhooker/internal/session" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/database" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/healthcheck" + "sneak.berlin/go/webhooker/internal/logger" + "sneak.berlin/go/webhooker/internal/session" ) // nolint:revive // HandlersParams is a standard fx naming convention diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 2afaeac..83bf1dd 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -6,16 +6,16 @@ import ( "testing" "time" - "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/healthcheck" - "git.eeqj.de/sneak/webhooker/internal/logger" - "git.eeqj.de/sneak/webhooker/internal/session" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/fx" "go.uber.org/fx/fxtest" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/database" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/healthcheck" + "sneak.berlin/go/webhooker/internal/logger" + "sneak.berlin/go/webhooker/internal/session" ) func TestHandleIndex(t *testing.T) { diff --git a/internal/handlers/index.go b/internal/handlers/index.go index 8d1901a..fb1a068 100644 --- a/internal/handlers/index.go +++ b/internal/handlers/index.go @@ -5,7 +5,7 @@ import ( "net/http" "time" - "git.eeqj.de/sneak/webhooker/internal/database" + "sneak.berlin/go/webhooker/internal/database" ) type IndexResponse struct { diff --git a/internal/healthcheck/healthcheck.go b/internal/healthcheck/healthcheck.go index dca9583..dc4d0ae 100644 --- a/internal/healthcheck/healthcheck.go +++ b/internal/healthcheck/healthcheck.go @@ -5,11 +5,11 @@ import ( "log/slog" "time" - "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/logger" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/database" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/logger" ) // nolint:revive // HealthcheckParams is a standard fx naming convention diff --git a/internal/logger/logger.go b/internal/logger/logger.go index acf3dc5..687b241 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -6,8 +6,8 @@ import ( "os" "time" - "git.eeqj.de/sneak/webhooker/internal/globals" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/globals" ) // nolint:revive // LoggerParams is a standard fx naming convention diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go index ca8ec10..76671b3 100644 --- a/internal/logger/logger_test.go +++ b/internal/logger/logger_test.go @@ -3,8 +3,8 @@ package logger import ( "testing" - "git.eeqj.de/sneak/webhooker/internal/globals" "go.uber.org/fx/fxtest" + "sneak.berlin/go/webhooker/internal/globals" ) func TestNew(t *testing.T) { diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 7d00744..abdba8c 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -6,9 +6,6 @@ import ( "net/http" "time" - "git.eeqj.de/sneak/webhooker/internal/config" - "git.eeqj.de/sneak/webhooker/internal/globals" - "git.eeqj.de/sneak/webhooker/internal/logger" basicauth "github.com/99designs/basicauth-go" "github.com/go-chi/chi/middleware" "github.com/go-chi/cors" @@ -16,6 +13,9 @@ import ( ghmm "github.com/slok/go-http-metrics/middleware" "github.com/slok/go-http-metrics/middleware/std" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/logger" ) // nolint:revive // MiddlewareParams is a standard fx naming convention diff --git a/internal/server/routes.go b/internal/server/routes.go index a59aad8..e9bb056 100644 --- a/internal/server/routes.go +++ b/internal/server/routes.go @@ -4,11 +4,11 @@ import ( "net/http" "time" - "git.eeqj.de/sneak/webhooker/static" sentryhttp "github.com/getsentry/sentry-go/http" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" + "sneak.berlin/go/webhooker/static" ) func (s *Server) SetupRoutes() { @@ -63,7 +63,7 @@ func (s *Server) SetupRoutes() { }) s.router.Get( - "/.well-known/healthcheck.json", + "/.well-known/healthcheck", s.h.HandleHealthCheck(), ) diff --git a/internal/server/server.go b/internal/server/server.go index 9fa08e4..da94588 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -10,12 +10,12 @@ import ( "syscall" "time" - "git.eeqj.de/sneak/webhooker/internal/config" - "git.eeqj.de/sneak/webhooker/internal/globals" - "git.eeqj.de/sneak/webhooker/internal/handlers" - "git.eeqj.de/sneak/webhooker/internal/logger" - "git.eeqj.de/sneak/webhooker/internal/middleware" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/globals" + "sneak.berlin/go/webhooker/internal/handlers" + "sneak.berlin/go/webhooker/internal/logger" + "sneak.berlin/go/webhooker/internal/middleware" "github.com/getsentry/sentry-go" "github.com/go-chi/chi" diff --git a/internal/session/session.go b/internal/session/session.go index 84e80bf..ed2a612 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -6,10 +6,10 @@ import ( "log/slog" "net/http" - "git.eeqj.de/sneak/webhooker/internal/config" - "git.eeqj.de/sneak/webhooker/internal/logger" "github.com/gorilla/sessions" "go.uber.org/fx" + "sneak.berlin/go/webhooker/internal/config" + "sneak.berlin/go/webhooker/internal/logger" ) const ( diff --git a/pkg/config/config.go b/pkg/config/config.go index 10a81ad..820c779 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -15,7 +15,7 @@ // // Usage: // -// import "git.eeqj.de/sneak/webhooker/pkg/config" +// import "sneak.berlin/go/webhooker/pkg/config" // // // Set the environment explicitly // config.SetEnvironment("prod") diff --git a/pkg/config/example_afero_test.go b/pkg/config/example_afero_test.go index fbada82..7fc0e64 100644 --- a/pkg/config/example_afero_test.go +++ b/pkg/config/example_afero_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "git.eeqj.de/sneak/webhooker/pkg/config" "github.com/spf13/afero" + "sneak.berlin/go/webhooker/pkg/config" ) // ExampleSetFs demonstrates how to use an in-memory filesystem for testing diff --git a/pkg/config/example_test.go b/pkg/config/example_test.go index 09fb230..007d7b6 100644 --- a/pkg/config/example_test.go +++ b/pkg/config/example_test.go @@ -5,7 +5,7 @@ import ( "log" "os" - "git.eeqj.de/sneak/webhooker/pkg/config" + "sneak.berlin/go/webhooker/pkg/config" ) func Example() { diff --git a/pkg/config/go.mod b/pkg/config/go.mod index 295d3e3..57aa52f 100644 --- a/pkg/config/go.mod +++ b/pkg/config/go.mod @@ -1,4 +1,4 @@ -module git.eeqj.de/sneak/webhooker/pkg/config +module sneak.berlin/go/webhooker/pkg/config go 1.23.0