From ba3fad9431edc98fb767a53117ec94f80f650591 Mon Sep 17 00:00:00 2001 From: Ivan Godwin Date: Thu, 16 Oct 2025 22:10:47 -0700 Subject: [PATCH] Tidying up --- Dockerfile | 10 +- Makefile | 119 +++++++++++++++++----- README.md | 44 ++++++-- cmd/grpcserver/main.go | 0 cmd/restserver/main.go | 174 -------------------------------- go.mod | 17 ++-- go.sum | 55 ++++++---- internal/config/config.go | 20 ++-- internal/domain/notification.go | 16 +-- internal/domain/notifier.go | 14 +-- internal/notifier/ntfy.go | 20 ++-- internal/notifier/slack.go | 14 +-- internal/queue/local.go | 18 ++-- 13 files changed, 225 insertions(+), 296 deletions(-) delete mode 100644 cmd/grpcserver/main.go delete mode 100644 cmd/restserver/main.go diff --git a/Dockerfile b/Dockerfile index b4e009b..3094cd5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,9 +16,8 @@ RUN go mod download # Copy source code COPY . . -# Build binaries +# Build binary RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o server ./cmd/server -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o restserver ./cmd/restserver # Runtime stage FROM alpine:latest @@ -33,9 +32,8 @@ RUN addgroup -g 1000 notifier && \ # Set working directory WORKDIR /app -# Copy binaries from builder +# Copy binary from builder COPY --from=builder /build/server /app/ -COPY --from=builder /build/restserver /app/ # Copy default config (can be overridden with volume mount) COPY config.yaml /app/config.yaml @@ -50,6 +48,6 @@ USER notifier # Expose ports EXPOSE 8080 50051 9090 8081 -# Default to running the combined server (handles both REST and gRPC) -# Can be overridden with docker run command +# Run server (defaults to both REST and gRPC) +# Override mode with environment variable: -e SERVER_MODE=rest or -e SERVER_MODE=grpc CMD ["/app/server"] diff --git a/Makefile b/Makefile index 631216a..2587970 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ -.PHONY: proto proto-gen proto-clean deps build run-grpc run-rest run-both test lint docker-build docker-run clean +.PHONY: proto proto-gen proto-clean deps build run run-grpc run-rest test lint fmt vet check docker-build docker-run clean help # Variables PROTO_DIR=api/grpc PROTO_FILE=$(PROTO_DIR)/notifier.proto PROTO_OUT=$(PROTO_DIR)/pb GO_MODULE=$(shell head -n 1 go.mod | awk '{print $$2}') +GO_FILES=$(shell find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./api/grpc/pb/*") # Generate protobuf code proto-gen: @@ -35,33 +36,78 @@ proto-deps: go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest @echo "Protoc plugins installed" -# Build binaries +# Build binary build: - @echo "Building binaries..." + @echo "Building binary..." @mkdir -p bin - go build -o bin/grpcserver ./cmd/grpcserver - go build -o bin/restserver ./cmd/restserver - @echo "Binaries built successfully" + go build -o bin/server ./cmd/server + @echo "Binary built successfully" -# Run gRPC server -run-grpc: - @echo "Running gRPC server..." - go run ./cmd/grpcserver/main.go +# Run server (default: both REST and gRPC) +run: + @echo "Running server (both REST and gRPC)..." + go run ./cmd/server/main.go -# Run REST server +# Run in REST-only mode run-rest: - @echo "Running REST server..." - go run ./cmd/restserver/main.go + @echo "Running server in REST-only mode..." + SERVER_MODE=rest go run ./cmd/server/main.go + +# Run in gRPC-only mode +run-grpc: + @echo "Running server in gRPC-only mode..." + SERVER_MODE=grpc go run ./cmd/server/main.go # Run tests test: @echo "Running tests..." go test -v -race -cover ./... -# Run linter +# Run tests with coverage report +test-coverage: + @echo "Running tests with coverage..." + go test -race -coverprofile=coverage.out -covermode=atomic ./... + go tool cover -html=coverage.out -o coverage.html + @echo "Coverage report generated: coverage.html" + +# Format code +fmt: + @echo "Formatting code..." + gofmt -s -w $(GO_FILES) + @echo "Code formatted" + +# Check formatting +fmt-check: + @echo "Checking code formatting..." + @if [ -n "$$(gofmt -l $(GO_FILES))" ]; then \ + echo "The following files need formatting:"; \ + gofmt -l $(GO_FILES); \ + exit 1; \ + fi + @echo "All files are properly formatted" + +# Run go vet +vet: + @echo "Running go vet..." + go vet ./... + @echo "go vet passed" + +# Run static analysis +check: fmt-check vet + @echo "Running static checks..." + go mod verify + @echo "All checks passed" + +# Run linter (requires golangci-lint) lint: @echo "Running linter..." + @which golangci-lint > /dev/null || (echo "golangci-lint not installed. Run: brew install golangci-lint" && exit 1) golangci-lint run ./... + @echo "Linting passed" + +# Run all quality checks +qa: fmt vet lint test + @echo "All quality checks passed!" # Build Docker image docker-build: @@ -84,15 +130,36 @@ clean: # Help help: @echo "Available targets:" - @echo " proto-gen - Generate protobuf code" - @echo " proto-clean - Clean generated protobuf code" - @echo " proto-deps - Install protoc plugins" - @echo " deps - Install Go dependencies" - @echo " build - Build binaries" - @echo " run-grpc - Run gRPC server" - @echo " run-rest - Run REST server" - @echo " test - Run tests" - @echo " lint - Run linter" - @echo " docker-build - Build Docker image" - @echo " docker-run - Run Docker container" - @echo " clean - Clean build artifacts" + @echo "" + @echo "Build:" + @echo " build - Build server binary" + @echo " clean - Clean build artifacts" + @echo "" + @echo "Run:" + @echo " run - Run server (both REST and gRPC)" + @echo " run-rest - Run server in REST-only mode" + @echo " run-grpc - Run server in gRPC-only mode" + @echo "" + @echo "Test:" + @echo " test - Run tests with race detector" + @echo " test-coverage - Run tests with coverage report" + @echo "" + @echo "Code Quality:" + @echo " fmt - Format code with gofmt" + @echo " fmt-check - Check if code is formatted" + @echo " vet - Run go vet" + @echo " lint - Run golangci-lint (requires installation)" + @echo " check - Run fmt-check + vet + mod verify" + @echo " qa - Run all quality checks (fmt + vet + lint + test)" + @echo "" + @echo "Protobuf:" + @echo " proto-gen - Generate protobuf code" + @echo " proto-clean - Clean generated protobuf code" + @echo " proto-deps - Install protoc plugins" + @echo "" + @echo "Dependencies:" + @echo " deps - Install Go dependencies" + @echo "" + @echo "Docker:" + @echo " docker-build - Build Docker image" + @echo " docker-run - Run Docker container" diff --git a/README.md b/README.md index a042f07..d641c6b 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ cd notifier go mod tidy # Build and run -go build -o bin/server ./cmd/server +make build ./bin/server ``` @@ -54,6 +54,13 @@ Server starts with: - **gRPC API** on `localhost:50051` - **Stdout notifier** enabled by default +**Run in different modes:** +```bash +./bin/server # Both REST and gRPC (default) +SERVER_MODE=rest ./bin/server # REST only +SERVER_MODE=grpc ./bin/server # gRPC only +``` + ### 2. Send Your First Notification ```bash @@ -299,6 +306,18 @@ docker run -d \ notifier:latest ``` +**Run in different modes:** +```bash +# Both REST and gRPC (default) +docker run -d -p 8080:8080 -p 50051:50051 notifier:latest + +# REST only +docker run -d -p 8080:8080 -e SERVER_MODE=rest notifier:latest + +# gRPC only +docker run -d -p 50051:50051 -e SERVER_MODE=grpc notifier:latest +``` + **Docker Compose:** ```bash docker-compose up -d @@ -391,9 +410,7 @@ notifier/ │ ├── router.go # Route configuration │ └── types.go # Request/response types ├── cmd/ -│ ├── server/main.go # Combined server (recommended) -│ ├── grpcserver/main.go # gRPC only -│ └── restserver/main.go # REST only +│ └── server/main.go # Unified server (configurable mode) ├── internal/ │ ├── config/ │ │ └── config.go # Configuration management @@ -434,14 +451,21 @@ notifier/ ### Build Commands ```bash -make build # Build binaries -make run-rest # Run REST server -make run-grpc # Run gRPC server (when implemented) -make test # Run tests -make lint # Run linter +make build # Build server binary +make run # Run server (both REST and gRPC) +make run-rest # Run server in REST-only mode +make run-grpc # Run server in gRPC-only mode +make test # Run tests with race detector +make test-coverage # Generate HTML coverage report +make fmt # Format code with gofmt +make vet # Run go vet +make lint # Run golangci-lint +make check # Run fmt-check + vet + mod verify +make qa # Run all quality checks make proto-gen # Generate protobuf code make docker-build # Build Docker image -make clean # Clean artifacts +make clean # Clean build artifacts +make help # Show all available targets ``` ### Adding a New Notifier diff --git a/cmd/grpcserver/main.go b/cmd/grpcserver/main.go deleted file mode 100644 index e69de29..0000000 diff --git a/cmd/restserver/main.go b/cmd/restserver/main.go deleted file mode 100644 index 0e5aeb6..0000000 --- a/cmd/restserver/main.go +++ /dev/null @@ -1,174 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/igodwin/notifier/api/rest" - "github.com/igodwin/notifier/internal/config" - "github.com/igodwin/notifier/internal/domain" - "github.com/igodwin/notifier/internal/notifier" - "github.com/igodwin/notifier/internal/queue" - "github.com/igodwin/notifier/internal/service" -) - -func main() { - // Load configuration - cfg, err := config.Load("") - if err != nil { - log.Printf("Warning: failed to load config, using defaults: %v", err) - cfg = getDefaultConfig() - } - - log.Printf("Starting Notifier REST Server on port %d", cfg.Server.RESTPort) - - // Create context - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // Initialize queue - var q domain.Queue - if cfg.Queue.Type == "local" { - q, err = queue.NewLocalQueue(cfg.Queue.Local) - if err != nil { - log.Fatalf("Failed to create queue: %v", err) - } - log.Println("Using local queue") - } else { - log.Fatalf("Queue type %s not implemented yet", cfg.Queue.Type) - } - - // Initialize notifier factory - factory := notifier.NewFactory() - - // Register notifiers based on configuration - if cfg.Notifiers.Stdout { - stdoutNotifier := notifier.NewStdoutNotifier() - if err := factory.RegisterNotifier(domain.TypeStdout, stdoutNotifier); err != nil { - log.Fatalf("Failed to register stdout notifier: %v", err) - } - log.Println("Registered stdout notifier") - } - - if cfg.Notifiers.SMTP != nil { - smtpNotifier, err := notifier.NewSMTPNotifier(cfg.Notifiers.SMTP) - if err != nil { - log.Printf("Warning: failed to create SMTP notifier: %v", err) - } else { - if err := factory.RegisterNotifier(domain.TypeEmail, smtpNotifier); err != nil { - log.Fatalf("Failed to register SMTP notifier: %v", err) - } - log.Println("Registered SMTP notifier") - } - } - - if cfg.Notifiers.Slack != nil { - slackNotifier, err := notifier.NewSlackNotifier(cfg.Notifiers.Slack) - if err != nil { - log.Printf("Warning: failed to create Slack notifier: %v", err) - } else { - if err := factory.RegisterNotifier(domain.TypeSlack, slackNotifier); err != nil { - log.Fatalf("Failed to register Slack notifier: %v", err) - } - log.Println("Registered Slack notifier") - } - } - - if cfg.Notifiers.Ntfy != nil { - ntfyNotifier, err := notifier.NewNtfyNotifier(cfg.Notifiers.Ntfy) - if err != nil { - log.Printf("Warning: failed to create Ntfy notifier: %v", err) - } else { - if err := factory.RegisterNotifier(domain.TypeNtfy, ntfyNotifier); err != nil { - log.Fatalf("Failed to register Ntfy notifier: %v", err) - } - log.Println("Registered Ntfy notifier") - } - } - - // Check if any notifiers are registered - if len(factory.SupportedTypes()) == 0 { - log.Fatal("No notifiers configured. Please enable at least one notifier in config.yaml") - } - - log.Printf("Supported notification types: %v", factory.SupportedTypes()) - - // Create notification service - svc := service.NewNotificationService(factory, q, cfg.Queue.WorkerCount) - - // Start workers - if err := svc.Start(ctx); err != nil { - log.Fatalf("Failed to start service: %v", err) - } - log.Printf("Started %d worker(s)", cfg.Queue.WorkerCount) - - // Create REST router - router := rest.NewRouter(svc) - - // Create HTTP server - addr := fmt.Sprintf("%s:%d", cfg.Server.Host, cfg.Server.RESTPort) - server := &http.Server{ - Addr: addr, - Handler: router, - ReadTimeout: 15 * time.Second, - WriteTimeout: 15 * time.Second, - IdleTimeout: 60 * time.Second, - } - - // Start server in goroutine - go func() { - log.Printf("REST server listening on %s", addr) - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("Failed to start server: %v", err) - } - }() - - // Wait for interrupt signal - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - <-sigChan - - log.Println("Shutting down server...") - - // Graceful shutdown - shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second) - defer shutdownCancel() - - if err := server.Shutdown(shutdownCtx); err != nil { - log.Printf("Error during server shutdown: %v", err) - } - - // Stop service - if err := svc.Stop(); err != nil { - log.Printf("Error stopping service: %v", err) - } - - log.Println("Server stopped") -} - -// getDefaultConfig returns a minimal default configuration -func getDefaultConfig() *config.Config { - return &config.Config{ - Server: config.ServerConfig{ - RESTPort: 8080, - Host: "0.0.0.0", - }, - Queue: domain.QueueConfig{ - Type: "local", - WorkerCount: 5, - RetryAttempts: 3, - Local: &domain.LocalQueueConfig{ - BufferSize: 1000, - }, - }, - Notifiers: config.NotifiersConfig{ - Stdout: true, - }, - } -} diff --git a/go.mod b/go.mod index 3d2a2a6..956755f 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,19 @@ module github.com/igodwin/notifier -go 1.23.2 +go 1.24.0 + +toolchain go1.24.6 require ( github.com/google/uuid v1.6.0 github.com/gorilla/mux v1.8.1 github.com/spf13/viper v1.19.0 - google.golang.org/grpc v1.62.1 - google.golang.org/protobuf v1.33.0 + google.golang.org/grpc v1.76.0 + google.golang.org/protobuf v1.36.10 ) require ( github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -27,10 +28,10 @@ require ( go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 8c0b93b..6135768 100644 --- a/go.sum +++ b/go.sum @@ -6,12 +6,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk 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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -59,27 +61,38 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= 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/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config/config.go b/internal/config/config.go index b7d38ae..58a7193 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -11,12 +11,12 @@ import ( // Config represents the application configuration type Config struct { - Server ServerConfig `mapstructure:"server"` - Queue domain.QueueConfig `mapstructure:"queue"` - Notifiers NotifiersConfig `mapstructure:"notifiers"` - Logging LoggingConfig `mapstructure:"logging"` - Metrics MetricsConfig `mapstructure:"metrics"` - HealthCheck HealthCheckConfig `mapstructure:"health_check"` + Server ServerConfig `mapstructure:"server"` + Queue domain.QueueConfig `mapstructure:"queue"` + Notifiers NotifiersConfig `mapstructure:"notifiers"` + Logging LoggingConfig `mapstructure:"logging"` + Metrics MetricsConfig `mapstructure:"metrics"` + HealthCheck HealthCheckConfig `mapstructure:"health_check"` } // ServerConfig contains server configuration @@ -44,10 +44,10 @@ type LoggingConfig struct { // MetricsConfig contains metrics/observability configuration type MetricsConfig struct { - Enabled bool `mapstructure:"enabled"` - Port int `mapstructure:"port"` - Path string `mapstructure:"path"` - PrometheusEnabled bool `mapstructure:"prometheus_enabled"` + Enabled bool `mapstructure:"enabled"` + Port int `mapstructure:"port"` + Path string `mapstructure:"path"` + PrometheusEnabled bool `mapstructure:"prometheus_enabled"` } // HealthCheckConfig contains health check configuration diff --git a/internal/domain/notification.go b/internal/domain/notification.go index 71f94fb..2ee88d8 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -104,12 +104,12 @@ type NotificationResult struct { // NotificationFilter is used for querying notifications type NotificationFilter struct { - IDs []string `json:"ids,omitempty"` - Types []NotificationType `json:"types,omitempty"` - Statuses []NotificationStatus `json:"statuses,omitempty"` - Recipients []string `json:"recipients,omitempty"` - CreatedAfter *time.Time `json:"created_after,omitempty"` - CreatedBefore *time.Time `json:"created_before,omitempty"` - Limit int `json:"limit,omitempty"` - Offset int `json:"offset,omitempty"` + IDs []string `json:"ids,omitempty"` + Types []NotificationType `json:"types,omitempty"` + Statuses []NotificationStatus `json:"statuses,omitempty"` + Recipients []string `json:"recipients,omitempty"` + CreatedAfter *time.Time `json:"created_after,omitempty"` + CreatedBefore *time.Time `json:"created_before,omitempty"` + Limit int `json:"limit,omitempty"` + Offset int `json:"offset,omitempty"` } diff --git a/internal/domain/notifier.go b/internal/domain/notifier.go index 8454d5e..02addc1 100644 --- a/internal/domain/notifier.go +++ b/internal/domain/notifier.go @@ -57,11 +57,11 @@ type NotificationService interface { // NotificationStats contains statistics about notification processing type NotificationStats struct { - TotalSent int64 `json:"total_sent"` - TotalFailed int64 `json:"total_failed"` - TotalPending int64 `json:"total_pending"` - TotalQueued int64 `json:"total_queued"` - ByType map[string]int64 `json:"by_type"` - ByStatus map[string]int64 `json:"by_status"` - AverageLatency float64 `json:"average_latency_ms"` + TotalSent int64 `json:"total_sent"` + TotalFailed int64 `json:"total_failed"` + TotalPending int64 `json:"total_pending"` + TotalQueued int64 `json:"total_queued"` + ByType map[string]int64 `json:"by_type"` + ByStatus map[string]int64 `json:"by_status"` + AverageLatency float64 `json:"average_latency_ms"` } diff --git a/internal/notifier/ntfy.go b/internal/notifier/ntfy.go index 24f5465..6a72ea2 100644 --- a/internal/notifier/ntfy.go +++ b/internal/notifier/ntfy.go @@ -43,17 +43,17 @@ type NtfyNotifier struct { // ntfyRequest represents the ntfy API request format type ntfyRequest struct { - Topic string `json:"topic"` - Message string `json:"message"` - Title string `json:"title,omitempty"` - Priority int `json:"priority,omitempty"` - Tags []string `json:"tags,omitempty"` - Click string `json:"click,omitempty"` - Attach string `json:"attach,omitempty"` + Topic string `json:"topic"` + Message string `json:"message"` + Title string `json:"title,omitempty"` + Priority int `json:"priority,omitempty"` + Tags []string `json:"tags,omitempty"` + Click string `json:"click,omitempty"` + Attach string `json:"attach,omitempty"` Actions []ntfyAction `json:"actions,omitempty"` - Icon string `json:"icon,omitempty"` - Delay string `json:"delay,omitempty"` - Email string `json:"email,omitempty"` + Icon string `json:"icon,omitempty"` + Delay string `json:"delay,omitempty"` + Email string `json:"email,omitempty"` } // ntfyAction represents an action button in ntfy diff --git a/internal/notifier/slack.go b/internal/notifier/slack.go index 5b48b9a..a91f286 100644 --- a/internal/notifier/slack.go +++ b/internal/notifier/slack.go @@ -30,17 +30,17 @@ type SlackNotifier struct { // slackMessage represents the Slack API request format type slackMessage struct { - Channel string `json:"channel,omitempty"` - Username string `json:"username,omitempty"` - IconEmoji string `json:"icon_emoji,omitempty"` - Text string `json:"text,omitempty"` - Blocks []slackBlock `json:"blocks,omitempty"` - Markdown bool `json:"mrkdwn,omitempty"` + Channel string `json:"channel,omitempty"` + Username string `json:"username,omitempty"` + IconEmoji string `json:"icon_emoji,omitempty"` + Text string `json:"text,omitempty"` + Blocks []slackBlock `json:"blocks,omitempty"` + Markdown bool `json:"mrkdwn,omitempty"` } // slackBlock represents a Slack block element type slackBlock struct { - Type string `json:"type"` + Type string `json:"type"` Text *slackTextBlock `json:"text,omitempty"` } diff --git a/internal/queue/local.go b/internal/queue/local.go index cf4f7bd..bccb7f2 100644 --- a/internal/queue/local.go +++ b/internal/queue/local.go @@ -8,20 +8,20 @@ import ( "sync" "time" - "github.com/igodwin/notifier/internal/domain" "github.com/google/uuid" + "github.com/igodwin/notifier/internal/domain" ) // LocalQueue is an in-memory queue implementation type LocalQueue struct { - queue chan *domain.QueueMessage - messages map[string]*domain.QueueMessage - mu sync.RWMutex - config *domain.LocalQueueConfig - persistToDisk bool - persistPath string - closed bool - closeChan chan struct{} + queue chan *domain.QueueMessage + messages map[string]*domain.QueueMessage + mu sync.RWMutex + config *domain.LocalQueueConfig + persistToDisk bool + persistPath string + closed bool + closeChan chan struct{} } // NewLocalQueue creates a new local queue instance