Tidying up
This commit is contained in:
+4
-6
@@ -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"]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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=
|
||||
|
||||
+10
-10
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
+10
-10
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user