From 40543925aa64217107300b9c64a6bbe8d67381cd Mon Sep 17 00:00:00 2001 From: Thanikan Date: Tue, 6 Oct 2026 17:05:28 +0530 Subject: [PATCH] refactor(notification)!: move notification to its own module, notifications notification lived in core's root module. A fix to it could only reach a consumer through a bump of the whole root module, which today also brings workflow's breaking changes. It is now the independent module github.com/OpenNSW/core/notifications, versioned on its own like storage (#159). The module takes a new path rather than keeping github.com/OpenNSW/core/notification. A consumer pinned to a root-module version that still contains notification/ would otherwise see the same import path in two modules and fail with an ambiguous import, so it could not adopt the module without that root bump. Under a new path it can require the module beside its existing root pin. The package keeps its name, notification, so a consumer changes only its import path and keeps using notification.X. goimports writes the import with the name spelled out, since it differs from the path. Only remote and secret are dependencies, nothing else in core imports it, and its behavior is unchanged. CI gets a Notifications Module job, and dependabot watches the new directory. BREAKING CHANGE: import notification "github.com/OpenNSW/core/notifications" instead of "github.com/OpenNSW/core/notification". The package name and API are unchanged. Co-Authored-By: Claude Opus 5.5 --- .github/dependabot.yml | 1 + .github/workflows/ci.yml | 35 +++++++++++++ README.md | 50 +++++++++---------- go.mod | 2 +- {notification => notifications}/README.md | 10 +++- {notification => notifications}/config.go | 0 .../config_test.go | 0 notifications/go.mod | 8 +++ notifications/go.sum | 12 +++++ {notification => notifications}/manager.go | 0 .../manager_test.go | 0 {notification => notifications}/provider.go | 0 .../providers/email.go | 2 +- .../providers/helpers.go | 0 .../providers/sms.go | 2 +- .../test/integration/sms_integration_test.go | 4 +- {notification => notifications}/types.go | 0 17 files changed, 94 insertions(+), 32 deletions(-) rename {notification => notifications}/README.md (85%) rename {notification => notifications}/config.go (100%) rename {notification => notifications}/config_test.go (100%) create mode 100644 notifications/go.mod create mode 100644 notifications/go.sum rename {notification => notifications}/manager.go (100%) rename {notification => notifications}/manager_test.go (100%) rename {notification => notifications}/provider.go (100%) rename {notification => notifications}/providers/email.go (97%) rename {notification => notifications}/providers/helpers.go (100%) rename {notification => notifications}/providers/sms.go (97%) rename {notification => notifications}/test/integration/sms_integration_test.go (95%) rename {notification => notifications}/types.go (100%) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7ba405aa..f0803b98 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,6 +17,7 @@ updates: - "/uiprojector" - "/trace" - "/storage" + - "/notifications" - "/json" - "/xmlgen" - "/htmlgen" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 67360984..0a449902 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -398,6 +398,41 @@ jobs: - name: Run Tests (includes build) run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + # The notifications/ package is its own Go module, so the root ./... jobs above do not + # cover it. Run its quality checks and tests independently. + notifications-module: + name: Notifications Module + runs-on: ubuntu-latest + permissions: + contents: read + defaults: + run: + working-directory: notifications + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-go@v7 + with: + go-version-file: 'notifications/go.mod' + cache-dependency-path: notifications/go.sum + cache: true + + - name: Verify go.mod + run: | + go mod tidy + git diff --exit-code go.mod go.sum + + - name: golangci-lint + uses: golangci/golangci-lint-action@v9 + with: + version: v2.11.2 + args: --timeout=5m + working-directory: notifications + + - name: Run Tests (includes build) + run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + # The uiprojector/ package is its own Go module, so the root ./... jobs above do not # cover it. Run its quality checks and tests independently. uiprojector-module: diff --git a/README.md b/README.md index 44ff729a..58f42b57 100644 --- a/README.md +++ b/README.md @@ -16,30 +16,30 @@ This SDK provides all the infrastructure pieces to build such a system, while ke ## Packages -| Package | Purpose | -|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`artifact`](artifact/README.md) | Versioned configuration registry — load workflow definitions, form schemas, and templates by ID (includes [`artifact/adapter`](artifact/adapter/README.md), bridge adapters for domain types) | -| [`authn`](authn/README.md) | JWT validation, identity context injection, and HTTP middleware | -| [`authz`](authz/README.md) | Scope-based authorization middleware and predicates, decoupled from authn | -| [`configyaml`](configyaml/README.md) | Loads a YAML config file into a Go struct, resolving `{{env:NAME}}` / `{{file:/path}}` placeholders via `secret.SecretRef` | -| [`cors`](cors/README.md) | CORS HTTP middleware | -| [`database`](database/README.md) | `database/sql` connection factory (PostgreSQL, MySQL, SQLite) with pooling and health checks | -| [`htmlgen`](htmlgen/README.md) | Print-ready HTML document generation from a static template and structured data, with contextual escaping | -| [`httputil`](httputil/README.md) | Shared HTTP response helpers for JSON payloads and correlation-ID-tagged API error bodies | -| [`json`](json/README.md) | Packages for working with decoded JSON documents (includes [`json/jsonpointer`](json/jsonpointer/README.md), RFC 6901 JSON Pointer get/set) | -| [`notification`](notification/README.md) | Multi-channel notification router (SMS, email) with pluggable providers | -| [`pagination`](pagination/README.md) | Standard pagination envelope and query parameter parsing | -| [`payment`](payment/README.md) | Pluggable payment gateway orchestration with webhook processing and idempotency | -| [`remote`](remote/README.md) | Registry-based outbound HTTP client with pluggable auth (API key, Bearer, OAuth2) | -| [`secret`](secret/README.md) | Secret-bearing config values (`SecretRef`) resolved from env, file, or literal sources | -| [`shared`](shared/README.md) | Small, dependency-free helpers (deep copy, nested map access, config validation) used across other modules | -| [`storage`](storage/README.md) | File storage abstraction (local filesystem and AWS S3) with presigned URLs | -| [`taskflow`](taskflow/README.md) | Micro-interactive task orchestration — the core engine for human-in-the-loop steps | -| [`temporal`](temporal/README.md) | Temporal client factory | -| [`trace`](trace/README.md) | HTTP request trace propagation and context-based trace ID correlation | -| [`uiprojector`](uiprojector/README.md) | Zone-based, metadata-driven UI rendering from workflow state and business data | -| [`workflow`](workflow/README.md) | JSON DSL-driven Temporal workflow graph interpreter | -| [`xmlgen`](xmlgen/README.md) | XML document generation from a static template and structured data, with automatic escaping | +| Package | Purpose | +|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`artifact`](artifact/README.md) | Versioned configuration registry — load workflow definitions, form schemas, and templates by ID (includes [`artifact/adapter`](artifact/adapter/README.md), bridge adapters for domain types) | +| [`authn`](authn/README.md) | JWT validation, identity context injection, and HTTP middleware | +| [`authz`](authz/README.md) | Scope-based authorization middleware and predicates, decoupled from authn | +| [`configyaml`](configyaml/README.md) | Loads a YAML config file into a Go struct, resolving `{{env:NAME}}` / `{{file:/path}}` placeholders via `secret.SecretRef` | +| [`cors`](cors/README.md) | CORS HTTP middleware | +| [`database`](database/README.md) | `database/sql` connection factory (PostgreSQL, MySQL, SQLite) with pooling and health checks | +| [`htmlgen`](htmlgen/README.md) | Print-ready HTML document generation from a static template and structured data, with contextual escaping | +| [`httputil`](httputil/README.md) | Shared HTTP response helpers for JSON payloads and correlation-ID-tagged API error bodies | +| [`json`](json/README.md) | Packages for working with decoded JSON documents (includes [`json/jsonpointer`](json/jsonpointer/README.md), RFC 6901 JSON Pointer get/set) | +| [`notifications`](notifications/README.md) | Multi-channel notification router (SMS, email) with pluggable providers | +| [`pagination`](pagination/README.md) | Standard pagination envelope and query parameter parsing | +| [`payment`](payment/README.md) | Pluggable payment gateway orchestration with webhook processing and idempotency | +| [`remote`](remote/README.md) | Registry-based outbound HTTP client with pluggable auth (API key, Bearer, OAuth2) | +| [`secret`](secret/README.md) | Secret-bearing config values (`SecretRef`) resolved from env, file, or literal sources | +| [`shared`](shared/README.md) | Small, dependency-free helpers (deep copy, nested map access, config validation) used across other modules | +| [`storage`](storage/README.md) | File storage abstraction (local filesystem and AWS S3) with presigned URLs | +| [`taskflow`](taskflow/README.md) | Micro-interactive task orchestration — the core engine for human-in-the-loop steps | +| [`temporal`](temporal/README.md) | Temporal client factory | +| [`trace`](trace/README.md) | HTTP request trace propagation and context-based trace ID correlation | +| [`uiprojector`](uiprojector/README.md) | Zone-based, metadata-driven UI rendering from workflow state and business data | +| [`workflow`](workflow/README.md) | JSON DSL-driven Temporal workflow graph interpreter | +| [`xmlgen`](xmlgen/README.md) | XML document generation from a static template and structured data, with automatic escaping | ## Requirements @@ -76,7 +76,7 @@ flowchart TD P["USER_INPUT · PAYMENT · EXTERNAL_REVIEW · API_CALL · custom"] end - SS["payment · remote · notification · storage"] + SS["payment · remote · notifications · storage"] AR[("artifact registry
workflow defs · task templates
form schemas · render templates")] Portal -->|HTTP| server diff --git a/go.mod b/go.mod index 1d290b82..44270b99 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.26.0 require ( github.com/OpenNSW/core/remote v0.8.0 - github.com/OpenNSW/core/secret v0.2.0 github.com/OpenNSW/core/shared v0.3.0 github.com/OpenNSW/core/uiprojector v0.3.0 github.com/aws/aws-sdk-go-v2 v1.47.1 @@ -21,6 +20,7 @@ require ( ) require ( + github.com/OpenNSW/core/secret v0.2.0 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.20 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.20.1 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.5.4 // indirect diff --git a/notification/README.md b/notifications/README.md similarity index 85% rename from notification/README.md rename to notifications/README.md index a7f05160..9e277277 100644 --- a/notification/README.md +++ b/notifications/README.md @@ -1,11 +1,17 @@ -# notification +# notifications + +`github.com/OpenNSW/core/notifications` is its own Go module. It moved from `github.com/OpenNSW/core/notification` in the root module. The package is still named `notification`, so only the import path changes; code keeps using `notification.X`. Import it with the name written out, as `goimports` writes it when a package's name differs from its path: + +```go +import notification "github.com/OpenNSW/core/notifications" +``` A multi-channel notification router with a pluggable provider model. Your application registers one provider per channel type (email, SMS, etc.); the manager dispatches each `Request` to the correct provider at runtime. ## Usage ```go -import "github.com/OpenNSW/core/notification" +import notification "github.com/OpenNSW/core/notifications" manager, err := notification.NewManager( notification.Config{ diff --git a/notification/config.go b/notifications/config.go similarity index 100% rename from notification/config.go rename to notifications/config.go diff --git a/notification/config_test.go b/notifications/config_test.go similarity index 100% rename from notification/config_test.go rename to notifications/config_test.go diff --git a/notifications/go.mod b/notifications/go.mod new file mode 100644 index 00000000..555ec1c8 --- /dev/null +++ b/notifications/go.mod @@ -0,0 +1,8 @@ +module github.com/OpenNSW/core/notifications + +go 1.26 + +require ( + github.com/OpenNSW/core/remote v0.8.0 + github.com/OpenNSW/core/secret v0.2.0 +) diff --git a/notifications/go.sum b/notifications/go.sum new file mode 100644 index 00000000..4460318d --- /dev/null +++ b/notifications/go.sum @@ -0,0 +1,12 @@ +github.com/OpenNSW/core/remote v0.8.0 h1:Ot44DtkEtzqKffy77EI0S6BCH1xDspQ2y2lSFKcZpPg= +github.com/OpenNSW/core/remote v0.8.0/go.mod h1:BzfC7ppzEAd5fUQmIsgeR2lZ6LzygNU4prNjvDpDH+4= +github.com/OpenNSW/core/secret v0.2.0 h1:7kxJYbVNJhN9X9/YyTUci4bv9oYKcm0nT/wBVrxBChQ= +github.com/OpenNSW/core/secret v0.2.0/go.mod h1:MEkX9vnlPRVTn+JQQsb192PoY6p3hbFSOIV+FxIMrc0= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/notification/manager.go b/notifications/manager.go similarity index 100% rename from notification/manager.go rename to notifications/manager.go diff --git a/notification/manager_test.go b/notifications/manager_test.go similarity index 100% rename from notification/manager_test.go rename to notifications/manager_test.go diff --git a/notification/provider.go b/notifications/provider.go similarity index 100% rename from notification/provider.go rename to notifications/provider.go diff --git a/notification/providers/email.go b/notifications/providers/email.go similarity index 97% rename from notification/providers/email.go rename to notifications/providers/email.go index 24384164..b9903863 100644 --- a/notification/providers/email.go +++ b/notifications/providers/email.go @@ -10,7 +10,7 @@ import ( "fmt" "net/http" - "github.com/OpenNSW/core/notification" + notification "github.com/OpenNSW/core/notifications" "github.com/OpenNSW/core/remote" "github.com/OpenNSW/core/remote/auth" "github.com/OpenNSW/core/secret" diff --git a/notification/providers/helpers.go b/notifications/providers/helpers.go similarity index 100% rename from notification/providers/helpers.go rename to notifications/providers/helpers.go diff --git a/notification/providers/sms.go b/notifications/providers/sms.go similarity index 97% rename from notification/providers/sms.go rename to notifications/providers/sms.go index a92b39c2..5eeba2ca 100644 --- a/notification/providers/sms.go +++ b/notifications/providers/sms.go @@ -10,7 +10,7 @@ import ( "fmt" "net/http" - "github.com/OpenNSW/core/notification" + notification "github.com/OpenNSW/core/notifications" "github.com/OpenNSW/core/remote" ) diff --git a/notification/test/integration/sms_integration_test.go b/notifications/test/integration/sms_integration_test.go similarity index 95% rename from notification/test/integration/sms_integration_test.go rename to notifications/test/integration/sms_integration_test.go index 36f1bfcf..0cf6c51a 100644 --- a/notification/test/integration/sms_integration_test.go +++ b/notifications/test/integration/sms_integration_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - "github.com/OpenNSW/core/notification" - "github.com/OpenNSW/core/notification/providers" + notification "github.com/OpenNSW/core/notifications" + "github.com/OpenNSW/core/notifications/providers" ) func TestSMSIntegration(t *testing.T) { diff --git a/notification/types.go b/notifications/types.go similarity index 100% rename from notification/types.go rename to notifications/types.go