diff --git a/Makefile b/Makefile index 84aeb8a..8c23427 100644 --- a/Makefile +++ b/Makefile @@ -16,12 +16,14 @@ test-frontend: .PHONY: test-backend lint-backend run-backend test-backend: - cd backend && go test -race ./... + cd backend/shared && go test -race ./... + cd backend/product && go test -race ./... lint-backend: - cd backend && go test ./internal/architecture + cd backend/product && go test ./architecture cd backend && test -z "$$(gofmt -l .)" - cd backend && go vet ./... + cd backend/shared && go vet ./... + cd backend/product && go vet ./... run-backend: - cd backend && go run ./cmd/bff + cd backend/product && go run . diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 748d279..6c3fdd8 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -25,11 +25,11 @@ skill to perform this completion workflow. ## Use the project error model - Do not introduce `fmt.Errorf` or `errors.New`. Use the constructors and - wrapping helpers in `backend/pkg/merrors`. + wrapping helpers in `backend/shared/errors`. - Preserve the underlying cause when adding context by using an appropriate `merrors` wrapping helper. - When no suitable project error exists, add the smallest generally useful - definition or helper to `backend/pkg/merrors` instead of creating an ad hoc error + definition or helper to `backend/shared/errors` instead of creating an ad hoc error elsewhere. ## Keep modules cohesive and names concise @@ -93,7 +93,18 @@ unused dependencies; remove those dependencies from the selected components. Copied BFF components are locally owned and may diverge. Use only Sendico's public HTTP API for platform operations. Never import sibling Sendico modules or access its database, internal gRPC APIs, discovery or message -bus. Keep the pinned public wire contract separate from local DTOs and models. +bus. Keep consumed public wire contracts inside the owning adapter, separate +from local DTOs and models. Do not copy unused API snapshots. Resolve the configured provider organization and customer resource ownership on the server. Never treat all customers as the backend integration principal. Use the copied structured logger, injected at operational boundaries. + +## Go module structure + +`backend/` groups independent Go modules; it is not itself a Go module. +`product/` is the customer-facing executable and owns its `main.go` and `go.mod`. +`shared/` is a library module for reusable infrastructure and has no entry point. +Backoffice belongs in its own executable module (`backoffice/`) when implemented, +with its own entry point and configuration, not inside the product executable. +Keep feature models, storage, services and transport together inside their owning +module. Shared infrastructure must not import a product or backoffice module. diff --git a/backend/PROVENANCE.json b/backend/PROVENANCE.json index c79f6f4..433f36c 100644 --- a/backend/PROVENANCE.json +++ b/backend/PROVENANCE.json @@ -4,1958 +4,1401 @@ "files": [ { "source": "api/pkg/merrors/classified.go", - "target": "pkg/merrors/classified.go", + "target": "shared/errors/classified.go", "blob": "569049273d437caa7d7d62e8f804f2d8b5ba632f", "adapted_beyond_namespace": false }, { "source": "api/pkg/merrors/errors.go", - "target": "pkg/merrors/errors.go", + "target": "shared/errors/errors.go", "blob": "dceb39670721ed0b4bc59e8da9a96a0428c87684", "adapted_beyond_namespace": true }, { "source": "api/pkg/merrors/errors_test.go", - "target": "pkg/merrors/errors_test.go", + "target": "shared/errors/errors_test.go", "blob": "4602c7cbc06b59f99059bc8e6e2780ba899f55a2", "adapted_beyond_namespace": false }, { "source": "api/pkg/merrors/invalid_argument.go", - "target": "pkg/merrors/invalid_argument.go", + "target": "shared/errors/invalid_argument.go", "blob": "954402a713c11f113377a08cdb27ef87006e40c3", "adapted_beyond_namespace": false }, { "source": "api/pkg/merrors/reason.go", - "target": "pkg/merrors/reason.go", + "target": "shared/errors/reason.go", "blob": "9205bac7ece87eaf56e410ff96ec68a0f0f69e51", "adapted_beyond_namespace": true }, { "source": "api/pkg/merrors/throttle.go", - "target": "pkg/merrors/throttle.go", + "target": "shared/errors/throttle.go", "blob": "3ff3da6a67b09911c14170fe04cadf8810f3a5f9", "adapted_beyond_namespace": false }, { "source": "api/pkg/merrors/throttle_test.go", - "target": "pkg/merrors/throttle_test.go", + "target": "shared/errors/throttle_test.go", "blob": "10014a308444208cc67fbefc61c0bb815697dafe", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/appversion/version.go", - "target": "edge/shared/appversion/version.go", + "target": "product/app/version/version.go", "blob": "85e224562f93e7e813b5bf5728cac8903c9d5cd7", "adapted_beyond_namespace": true }, { "source": "api/pkg/version/factory/factory.go", - "target": "pkg/version/factory/factory.go", + "target": "shared/version/factory/factory.go", "blob": "be90064ac5e583446678fdbbc12126c57e82ced6", "adapted_beyond_namespace": false }, { "source": "api/pkg/version/internal/version.go", - "target": "pkg/version/internal/version.go", + "target": "shared/version/internal/version.go", "blob": "15d6674c3d6c3b5ac75ce455b228fb01d6003639", "adapted_beyond_namespace": false }, { "source": "api/pkg/version/info.go", - "target": "pkg/version/info.go", + "target": "shared/version/info.go", "blob": "c9a9ce553da2905e1415d414ed190029b13e53e2", "adapted_beyond_namespace": false }, { "source": "api/pkg/version/version.go", - "target": "pkg/version/version.go", + "target": "shared/version/version.go", "blob": "7ab617fe20f65d080795cfa7d3bf8c3b21b96126", "adapted_beyond_namespace": false }, { "source": "api/pkg/server/main/run.go", - "target": "pkg/server/main/run.go", + "target": "product/app/process/run/run.go", "blob": "103dbeaf5fa7bb533dad3ce2c5e0aed1cd8667c7", "adapted_beyond_namespace": true }, { "source": "api/pkg/server/internal/instance.go", - "target": "pkg/server/internal/instance.go", + "target": "product/app/process/internal/instance.go", "blob": "e1d8f2a5b4cd6df2740562632c49cae5ee336d77", "adapted_beyond_namespace": false }, { "source": "api/pkg/server/internal/server.go", - "target": "pkg/server/internal/server.go", + "target": "product/app/process/internal/server.go", "blob": "b841af395a1ceaaedc337000cb75ec44834e27d3", "adapted_beyond_namespace": true }, { "source": "api/pkg/server/factory.go", - "target": "pkg/server/factory.go", + "target": "product/app/process/factory.go", "blob": "a91e7251de0d882cfb389fc155d636d2872b9749", "adapted_beyond_namespace": false }, { "source": "api/pkg/server/server.go", - "target": "pkg/server/server.go", + "target": "product/app/process/server.go", "blob": "127219004cdea545dc0da6f11026e18522449989", "adapted_beyond_namespace": false }, { "source": "api/pkg/mlogger/logger.go", - "target": "pkg/mlogger/logger.go", + "target": "shared/logging/logger.go", "blob": "71172f8361d8185d1b86106e9de5e568cc39fe8e", "adapted_beyond_namespace": false }, { "source": "api/pkg/mlogger/logger_test.go", - "target": "pkg/mlogger/logger_test.go", + "target": "shared/logging/logger_test.go", "blob": "9c7b9e8b6981c8a363810d84dd2050a872872499", "adapted_beyond_namespace": false }, { "source": "api/pkg/mlogger/factory/mlogger.go", - "target": "pkg/mlogger/factory/mlogger.go", + "target": "shared/logging/factory/mlogger.go", "blob": "d3315d2c6b54c44963c23900d5a84379bb1104ff", "adapted_beyond_namespace": false }, { "source": "api/pkg/mlogger/internal/mlogger/mlogger.go", - "target": "pkg/mlogger/internal/mlogger/mlogger.go", + "target": "shared/logging/internal/mlogger.go", "blob": "ad7c579f7c5a26668f171853677b52424a2f203f", "adapted_beyond_namespace": false }, { "source": "api/pkg/mservice/mservice.go", - "target": "pkg/mservice/mservice.go", + "target": "shared/service/mservice.go", "blob": "87e24f67707a64084f6eaf4d7ed9b4e14c17177c", "adapted_beyond_namespace": false }, { "source": "api/pkg/mservice/services.go", - "target": "pkg/mservice/services.go", + "target": "shared/service/services.go", "blob": "d02e6f5b5af4ba7dac1fdd1cf3f6331ecd1800a1", "adapted_beyond_namespace": true }, { "source": "api/pkg/model/account.go", - "target": "pkg/model/account.go", + "target": "product/customers/model/account.go", "blob": "552c87f19dedb4927b3758d7ca86f091be8e2f34", "adapted_beyond_namespace": true }, { "source": "api/pkg/model/archivable.go", - "target": "pkg/model/archivable.go", + "target": "shared/model/archivable.go", "blob": "9b51c661f5c09825a6ea8dfc46aed09f94166654", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/client.go", - "target": "pkg/model/client.go", + "target": "product/sessions/model/client.go", "blob": "d61c63a55985b8b3bf05bb417f1079ab98fbf495", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/describable.go", - "target": "pkg/model/describable.go", + "target": "shared/model/describable.go", "blob": "1c9b1ce051384b5b8cab4388a6be547bc9b2c1cd", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/fconfig.go", - "target": "pkg/model/fconfig.go", + "target": "shared/model/fconfig.go", "blob": "8806c3b3c30f17033a8b1a0228b445c437f7b50d", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/refresh.go", - "target": "pkg/model/refresh.go", + "target": "product/sessions/model/refresh.go", "blob": "43e956be1babedf7857f7697dd3478eb9d05b006", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/sessionid.go", - "target": "pkg/model/sessionid.go", + "target": "product/sessions/model/sessionid.go", "blob": "251eea9f0bb56d67d89d32851e004313d17aae52", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/userdata.go", - "target": "pkg/model/userdata.go", + "target": "product/customers/model/userdata.go", "blob": "8a56bd19ffececc4ede58155263eff8e50721101", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/model/verificaton.go", - "target": "pkg/model/verificaton.go", + "target": "product/verification/model/verificaton.go", "blob": "4367db5eaaebdb9caea1815dca8d641adff05a42", "adapted_beyond_namespace": false }, { "source": "api/pkg/model/viewcursor.go", - "target": "pkg/model/viewcursor.go", + "target": "shared/model/viewcursor.go", "blob": "0143c3f99f3131f778fba713c33f796fb4fb9b6e", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/storable/id.go", - "target": "pkg/db/storable/id.go", + "target": "shared/database/record/id.go", "blob": "58d06374158e2e606aa98fc21860a5077b26b934", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/storable/ref.go", - "target": "pkg/db/storable/ref.go", + "target": "shared/database/record/ref.go", "blob": "58e346847d00d8e70f435fc1346a540f76241e83", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/storable/storable.go", - "target": "pkg/db/storable/storable.go", + "target": "shared/database/record/storable.go", "blob": "e048582bbc669013eaf02ea57076072a3d4d16e6", "adapted_beyond_namespace": false }, { "source": "api/pkg/mutil/mzap/account.go", - "target": "pkg/mutil/mzap/account.go", + "target": "shared/logging/fields/account.go", "blob": "628c21d887583a26b6f804c284b9383213b17627", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/mutil/mzap/error.go", - "target": "pkg/mutil/mzap/error.go", + "target": "shared/logging/fields/error.go", "blob": "e2c47cb94bdbd8bafe87022002cd86168713091c", "adapted_beyond_namespace": false }, { "source": "api/pkg/mutil/mzap/object.go", - "target": "pkg/mutil/mzap/object.go", + "target": "shared/logging/fields/object.go", "blob": "663574556a14cd592fcaa3be615bed3644f7dea1", "adapted_beyond_namespace": true }, { "source": "api/pkg/mutil/mask/mask.go", - "target": "pkg/mutil/mask/mask.go", + "target": "shared/logging/mask/mask.go", "blob": "9d3ea7912594b23d51ea0e06262d5a2a7f1ff22e", "adapted_beyond_namespace": false }, { "source": "api/pkg/mutil/mask/mask_test.go", - "target": "pkg/mutil/mask/mask_test.go", + "target": "shared/logging/mask/mask_test.go", "blob": "6c723a9554c63a719e10c2dc3f1dc1267a698d38", "adapted_beyond_namespace": false }, { "source": "api/edge/product/interface/accountservice/types.go", - "target": "edge/product/interface/accountservice/types.go", + "target": "product/customers/types.go", "blob": "848519b9000cda122be8fa652544b33ed2f7fd52", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/config.go", - "target": "pkg/db/config.go", + "target": "shared/database/config.go", "blob": "86d46f9f0c78e76e782f134222028fe91d955f53", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/connection.go", - "target": "pkg/db/connection.go", + "target": "shared/database/connection.go", "blob": "ce8914683aa4cee98e6a0d75c8622329b3500995", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/factory.go", - "target": "pkg/db/factory.go", + "target": "shared/database/factory.go", "blob": "b9ea3bb4da4fae375d9fabe7c816c7a6b65847bf", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/transaction/context.go", - "target": "pkg/db/transaction/context.go", + "target": "shared/database/transaction/context.go", "blob": "0b89e5fe74e08ecdd5709128acbe4edd40980fb2", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/transaction/context_test.go", - "target": "pkg/db/transaction/context_test.go", + "target": "shared/database/transaction/context_test.go", "blob": "5f0c7ed46bd3ccafbf987106814622115fa77570", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/transaction/factory.go", - "target": "pkg/db/transaction/factory.go", + "target": "shared/database/transaction/factory.go", "blob": "d26e313f69414ce7fecb63169450934d0255007a", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/transaction/transaction.go", - "target": "pkg/db/transaction/transaction.go", + "target": "shared/database/transaction/transaction.go", "blob": "859977f653db2e0dac676cd12f161acc2d91b72b", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/template/interface.go", - "target": "pkg/db/template/interface.go", + "target": "shared/database/template/interface.go", "blob": "cbf1657146d27705999f732f0e5fad11678f5c64", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/template/template.go", - "target": "pkg/db/template/template.go", + "target": "shared/database/template/template.go", "blob": "abe24d42447e1759aa373a5ecadd06ccb879d672", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/repository/builder/accumulator.go", - "target": "pkg/db/repository/builder/accumulator.go", + "target": "shared/database/repository/builder/accumulator.go", "blob": "7d2729a40035770a43f7cbe12e07aae93dd7f48d", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/alias.go", - "target": "pkg/db/repository/builder/alias.go", + "target": "shared/database/repository/builder/alias.go", "blob": "ba587bc8b89263624167daf56156d47ba82c21eb", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/array.go", - "target": "pkg/db/repository/builder/array.go", + "target": "shared/database/repository/builder/array.go", "blob": "59e78a3b50b35b037442ea488f14bb61608f65e1", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/expression.go", - "target": "pkg/db/repository/builder/expression.go", + "target": "shared/database/repository/builder/expression.go", "blob": "0c1d284e0236891be603aea6e75bbce857b07883", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/field.go", - "target": "pkg/db/repository/builder/field.go", + "target": "shared/database/repository/builder/field.go", "blob": "4d2caaa547588d3e6b55b5a9e3f79e4b6efef84c", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/keyword.go", - "target": "pkg/db/repository/builder/keyword.go", + "target": "shared/database/repository/builder/keyword.go", "blob": "64d67b0c826821b7d679a8c76db5db105dd06982", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/operators.go", - "target": "pkg/db/repository/builder/operators.go", + "target": "shared/database/repository/builder/operators.go", "blob": "c54bbd18c6ebf873b1587254a362f88f86ba92be", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/patch.go", - "target": "pkg/db/repository/builder/patch.go", + "target": "shared/database/repository/builder/patch.go", "blob": "5da3eeebac492cf46ca2d099617e2898395b9b96", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/pipeline.go", - "target": "pkg/db/repository/builder/pipeline.go", + "target": "shared/database/repository/builder/pipeline.go", "blob": "47350e0d61eaecbdfa36c3cac8106b2f50fb7c44", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/projection.go", - "target": "pkg/db/repository/builder/projection.go", + "target": "shared/database/repository/builder/projection.go", "blob": "466f07a7dbc9d0fd4737df1dc601cdf0807d83ca", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/query.go", - "target": "pkg/db/repository/builder/query.go", + "target": "shared/database/repository/builder/query.go", "blob": "1fc397a04e2c1266f4abffb8785140c6ba770be2", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/unwind.go", - "target": "pkg/db/repository/builder/unwind.go", + "target": "shared/database/repository/builder/unwind.go", "blob": "9ed2c689ffb378b019706b2a2923cbae048df5b9", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builder/value.go", - "target": "pkg/db/repository/builder/value.go", + "target": "shared/database/repository/builder/value.go", "blob": "45b011c84f129e508231c314b4935c0f33bf08ad", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/builders.go", - "target": "pkg/db/repository/builders.go", + "target": "shared/database/repository/builders.go", "blob": "ba4190e467397693c5dfce8c991bfbbd2fcf7cd5", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/repository/cursor.go", - "target": "pkg/db/repository/cursor.go", + "target": "shared/database/repository/cursor.go", "blob": "bfcbb351acc317539655053a5ff7861d1c987fdf", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/db/repository/repository.go", - "target": "pkg/db/repository/repository.go", + "target": "shared/database/repository/repository.go", "blob": "14c4c10705cc641fe788dd0b10d9e1344c699599", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/repository/index/index.go", - "target": "pkg/db/repository/index/index.go", + "target": "shared/database/repository/index/index.go", "blob": "a689631c9eaa3b5d34198452633efa2c853a5543", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/index/types.go", - "target": "pkg/db/repository/index/types.go", + "target": "shared/database/repository/index/types.go", "blob": "281ec1d8a98e3bd8ba21bff3d0769e3048888993", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/repository/decoder/decoder.go", - "target": "pkg/db/repository/decoder/decoder.go", + "target": "shared/database/repository/decoder/decoder.go", "blob": "3a5fb78ead22a12649817946d031a1f092fb9d35", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/index.go", - "target": "pkg/db/internal/mongo/repositoryimp/index.go", + "target": "shared/database/internal/repository/index.go", "blob": "ea49d7875b38d296d98012cb801319076254639a", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/index_integration_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/index_integration_test.go", + "target": "shared/database/internal/repository/index_integration_test.go", "blob": "52a6f3d257266124897cdfde952dc9deb84e19f0", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/index_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/index_test.go", + "target": "shared/database/internal/repository/index_test.go", "blob": "a84a6a225c1aa9c822936d9885d9141f87a19eba", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository.go", + "target": "shared/database/internal/repository/repository.go", "blob": "28d3683fc0b9ad9ce2ba8dc04eebad0247c04465", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository_comprehensive_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository_comprehensive_test.go", + "target": "shared/database/internal/repository/repository_comprehensive_test.go", "blob": "1395cc67768367047dcee5b39ac60725f58e546c", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository_filter.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository_filter.go", + "target": "shared/database/internal/repository/repository_filter.go", "blob": "05b5698e2b28a39e3150b8893dcd687809e6f09f", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository_insertmany_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository_insertmany_test.go", + "target": "shared/database/internal/repository/repository_insertmany_test.go", "blob": "1b4836cdbede198b5f8ca7a6eebd6d8f07b8f635", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository_patch_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository_patch_test.go", + "target": "shared/database/internal/repository/repository_patch_test.go", "blob": "8a7ae9ba0caf91d866853ba260253a1979868155", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository_test.go", + "target": "shared/database/internal/repository/repository_test.go", "blob": "89f5cb2c61aabf58a7d0440d62bd1d93067f5204", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/repository_upsert_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/repository_upsert_test.go", + "target": "shared/database/internal/repository/repository_upsert_test.go", "blob": "acffdb9f7913b43bfd2457beb5fd9cfd42b63b03", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/accumulator.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/accumulator.go", + "target": "shared/database/internal/builder/accumulator.go", "blob": "09ff732bd77561ba180d7af84b1c7b1e306cac40", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/alias.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/alias.go", + "target": "shared/database/internal/builder/alias.go", "blob": "325f315782b4988774fb1c54a5efd7d242e26c5a", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/array.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/array.go", + "target": "shared/database/internal/builder/array.go", "blob": "738fc9e1bcd3274475a435b602985b1cd384f125", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/expression.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/expression.go", + "target": "shared/database/internal/builder/expression.go", "blob": "376a2fb8ba00ff256918652de46f72db6909f18b", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/field.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/field.go", + "target": "shared/database/internal/builder/field.go", "blob": "3a864b1a4872f7a4abaf76d7b0d98a52a1b4f16b", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/func.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/func.go", + "target": "shared/database/internal/builder/func.go", "blob": "b69410e9226a9dc540bc06f2b2acafd78c293106", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/gaccumulator.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/gaccumulator.go", + "target": "shared/database/internal/builder/gaccumulator.go", "blob": "762a5da23a2f5fa2258689358a9a8f61aca6a8db", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/patch.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/patch.go", + "target": "shared/database/internal/builder/patch.go", "blob": "d5519688e724745d2c20b70e98cf2e6ecbf94750", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/patch_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/patch_test.go", + "target": "shared/database/internal/builder/patch_test.go", "blob": "ef86937d1c3c65dc0e7d8bd4b02ac5d262e68097", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/pipeline.go", + "target": "shared/database/internal/builder/pipeline.go", "blob": "8ab981135123f96df0a537723ca9199d582daa3e", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go", + "target": "shared/database/internal/builder/pipeline_test.go", "blob": "25a2989ae5f059426fed2ad90a84abdf614a31c2", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/projection.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/projection.go", + "target": "shared/database/internal/builder/projection.go", "blob": "e53ec47bcfe487d5d9e8a12c5d8ea21c9d3056d9", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/query.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/query.go", + "target": "shared/database/internal/builder/query.go", "blob": "5aaa57826cd4af46209ec1bf3190b74ef984bb23", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/query_test.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/query_test.go", + "target": "shared/database/internal/builder/query_test.go", "blob": "c915baaa5a26d51349c58971ed44dbcca9b040c9", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/repositoryimp/builderimp/value.go", - "target": "pkg/db/internal/mongo/repositoryimp/builderimp/value.go", + "target": "shared/database/internal/builder/value.go", "blob": "10de4fa645cc76a6e6fdb369177d63bf5f723974", "adapted_beyond_namespace": false }, { "source": "api/pkg/mutil/db/archive.go", - "target": "pkg/mutil/db/archive.go", + "target": "shared/database/query/archive.go", "blob": "d4205a5d03dc534da82b6448339a5b5fdb61e09e", "adapted_beyond_namespace": false }, { "source": "api/pkg/mutil/db/array.go", - "target": "pkg/mutil/db/array.go", + "target": "shared/database/query/array.go", "blob": "b0fb240cb832169a753bd4aa5076ec5c33dd5b26", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/mutil/db/db.go", - "target": "pkg/mutil/db/db.go", + "target": "shared/database/query/db.go", "blob": "d29855c92fe4df1abf5e342d5f025c4e6e7434ab", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/db.go", - "target": "pkg/db/internal/mongo/db.go", + "target": "shared/database/internal/mongo/db.go", "blob": "ae2659cc356ef53a5a228ec387c516be27b9d2bc", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/internal/mongo/mongo.go", - "target": "pkg/db/internal/mongo/mongo.go", + "target": "shared/database/internal/mongo/mongo.go", "blob": "45915bc5409969720ff7d5e3558ac6f7a70d9758", "adapted_beyond_namespace": false }, { "source": "api/pkg/mutil/config/param.go", - "target": "pkg/mutil/config/param.go", + "target": "shared/config/param.go", "blob": "b2dad64a6b9167e686c04ab0cc28f0173a4fb4a5", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/transactionimp/factory.go", - "target": "pkg/db/internal/mongo/transactionimp/factory.go", + "target": "shared/database/internal/transaction/factory.go", "blob": "6b5c0424c6b8a27c5607d6bfb7d06ac2745dee0d", "adapted_beyond_namespace": false }, { "source": "api/pkg/db/internal/mongo/transactionimp/transaction.go", - "target": "pkg/db/internal/mongo/transactionimp/transaction.go", + "target": "shared/database/internal/transaction/transaction.go", "blob": "266feed66a033d86f8c75785718d424ac10f8db8", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/internal/mongo/accountdb/db.go", - "target": "pkg/db/internal/mongo/accountdb/db.go", + "target": "product/customers/store/mongo/db.go", "blob": "43c302e80b8b1f931b02d5595a5ccd19bf95543c", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/db/internal/mongo/accountdb/user.go", - "target": "pkg/db/internal/mongo/accountdb/user.go", + "target": "product/customers/store/mongo/user.go", "blob": "22d68e5e07e5ac002c661e07409e3f21ba5c475c", "adapted_beyond_namespace": true }, { "source": "api/pkg/db/account/account.go", - "target": "pkg/db/account/account.go", + "target": "product/customers/store/account.go", "blob": "22f6191c47730d7e1373e96c8aa691940525fcf3", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/middleware/cors_test.go", - "target": "edge/shared/interface/middleware/cors_test.go", + "target": "product/auth/config/cors_test.go", "blob": "613ab83cd921e3e0a59ea40be3d7e226abdfcec0", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/middleware/login_rate_limit.go", - "target": "edge/shared/interface/middleware/login_rate_limit.go", + "target": "product/auth/config/login_rate_limit.go", "blob": "8b09e5c11e80e2af12341e7084a0a91b616fcbf0", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/middleware/login_rate_limit_test.go", - "target": "edge/shared/interface/middleware/login_rate_limit_test.go", + "target": "product/auth/config/login_rate_limit_test.go", "blob": "c5991e9e3f0966feca907c49423b7757d41ecc21", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/middleware/middleware.go", - "target": "edge/shared/interface/middleware/middleware.go", + "target": "product/auth/config/middleware.go", "blob": "be197a8313920db99c2c002417feb063dc3273d4", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/middleware/token_test.go", - "target": "edge/shared/interface/middleware/token_test.go", + "target": "product/auth/config/token_test.go", "blob": "090f6fe0041c8dc71658bcdf63cacf2791c3bc66", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/ratelimit/config.go", - "target": "edge/shared/interface/ratelimit/config.go", + "target": "shared/ratelimit/config.go", "blob": "1854a6abeadc2a9ce014e79f124c8cb213e17a65", "adapted_beyond_namespace": false }, { "source": "api/edge/product/interface/accountservice/internal/service.go", - "target": "edge/product/interface/accountservice/internal/service.go", + "target": "product/customers/internal/service.go", "blob": "e0a26dd3f3d740ad5103e98a56bfa68f1065b1b6", "adapted_beyond_namespace": true }, { "source": "api/edge/product/interface/accountservice/internal/service_test.go", - "target": "edge/product/interface/accountservice/internal/service_test.go", + "target": "product/customers/internal/service_test.go", "blob": "49dedb715e1861fdf2e5dfdbea870964a2a740b1", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/consume.go", - "target": "edge/shared/storage/verification/consume.go", + "target": "product/verification/store/consume.go", "blob": "95b713591d6bc13004b0c83ceeece35bc88893b6", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/consume_logging.go", - "target": "edge/shared/storage/verification/consume_logging.go", + "target": "product/verification/store/consume_logging.go", "blob": "250b5e3a3850d1d163a45709d424d75eb7f53f3e", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/consume_lookup.go", - "target": "edge/shared/storage/verification/consume_lookup.go", + "target": "product/verification/store/consume_lookup.go", "blob": "597d2a9cad6deae6537c48b36cd5af813477acc9", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/consume_scope.go", - "target": "edge/shared/storage/verification/consume_scope.go", + "target": "product/verification/store/consume_scope.go", "blob": "835c3618a0c80617e21146f358057e9f56c34adb", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/consume_update.go", - "target": "edge/shared/storage/verification/consume_update.go", + "target": "product/verification/store/consume_update.go", "blob": "c0c58007b9a2a076918bacf2d88a386bc84713a2", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/context_ref.go", - "target": "edge/shared/storage/verification/context_ref.go", + "target": "product/verification/store/context_ref.go", "blob": "a0cde6224e47fd8261b8a18fcac1ece12df3ecbc", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/context_ref_test.go", - "target": "edge/shared/storage/verification/context_ref_test.go", + "target": "product/verification/store/context_ref_test.go", "blob": "71fc7eff86bd263b0070303b1feb49415d360a4c", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/create.go", - "target": "edge/shared/storage/verification/create.go", + "target": "product/verification/store/create.go", "blob": "af1b06d68c0123f8ee222fe762e4167fc42da8e7", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/db.go", - "target": "edge/shared/storage/verification/db.go", + "target": "product/verification/store/db.go", "blob": "8e8e89d262d5c934177a15f495446e80690cbed9", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/errors.go", - "target": "edge/shared/storage/verification/errors.go", + "target": "product/verification/store/errors.go", "blob": "232dcadf98a3af9f80b1d0f6dd9760fd05a0b7f8", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/verification/hash.go", - "target": "edge/shared/storage/verification/hash.go", + "target": "product/verification/store/hash.go", "blob": "36fadcf404bfc77608b28cca1e669a4a62f31774", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/hash_test.go", - "target": "edge/shared/storage/verification/hash_test.go", + "target": "product/verification/store/hash_test.go", "blob": "342d56cd5f13ca7716de491a37a9da8851c6b42b", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/verification/idempotency.go", - "target": "edge/shared/storage/verification/idempotency.go", + "target": "product/verification/store/idempotency.go", "blob": "0d74b5908dc775dc1d03f9fdfe2ce9a37e25d1f8", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/issuance_limit.go", - "target": "edge/shared/storage/verification/issuance_limit.go", + "target": "product/verification/store/issuance_limit.go", "blob": "052f1247e52b8c7e329009adda53ee518173e18f", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/verification/issuance_limit_test.go", - "target": "edge/shared/storage/verification/issuance_limit_test.go", + "target": "product/verification/store/issuance_limit_test.go", "blob": "01e81a5d2c8dd39ab07aeb6722f5bab17bf6ab3b", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/memory_token_repository_test.go", - "target": "edge/shared/storage/verification/memory_token_repository_test.go", + "target": "product/verification/store/memory_token_repository_test.go", "blob": "3a24298b02e90323416f1b47a1012d36b7cd26bc", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/request.go", - "target": "edge/shared/storage/verification/request.go", + "target": "product/verification/store/request.go", "blob": "4d767260e3136a4688bfe011a328967826213da8", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/token_query_matcher_test.go", - "target": "edge/shared/storage/verification/token_query_matcher_test.go", + "target": "product/verification/store/token_query_matcher_test.go", "blob": "9a5c088919884056e793b3a791e159c7ce4c0927", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/verification.go", - "target": "edge/shared/storage/verification/verification.go", + "target": "product/verification/store/verification.go", "blob": "2e4b0e9bb12612b3766bf40c56abdb4f6efe3245", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/verification_attempts_test.go", - "target": "edge/shared/storage/verification/verification_attempts_test.go", + "target": "product/verification/store/verification_attempts_test.go", "blob": "eb2cbc58cbe68606dc152a23568fc34ec5084f32", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/verification_idempotency_test.go", - "target": "edge/shared/storage/verification/verification_idempotency_test.go", + "target": "product/verification/store/verification_idempotency_test.go", "blob": "35e38cc81794dcdd4b4d7ef07efda2d6736080aa", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/verification_test.go", - "target": "edge/shared/storage/verification/verification_test.go", + "target": "product/verification/store/verification_test.go", "blob": "d0eaab49114e1c7c9a3fa0d609cb695566585a22", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/verification/verification_test_helpers_test.go", - "target": "edge/shared/storage/verification/verification_test_helpers_test.go", + "target": "product/verification/store/verification_test_helpers_test.go", "blob": "83f5a63f4f057befd01c83b8e667953ba3199a0f", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/ratelimits/db.go", - "target": "edge/shared/storage/ratelimits/db.go", + "target": "shared/ratelimit/store/db.go", "blob": "6d5d118198d62f545f0ef10216466c4cbec9b16d", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/mongo/dependencies.go", - "target": "edge/shared/storage/mongo/dependencies.go", + "target": "shared/database/dependencies/dependencies.go", "blob": "4aa6a89fb359a305ddc74d6e4e9bdcdb50cadb49", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/server/server.go", - "target": "edge/shared/server/server.go", + "target": "product/app/server/server.go", "blob": "186175bab73d2f2368f45fb5ad16854afec81a68", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/server/server.go", - "target": "edge/shared/internal/server/server.go", + "target": "product/app/server/internal/server.go", "blob": "e6a780272db88437ce4928cb5cb07610754628ce", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/server/server_test.go", - "target": "edge/shared/internal/server/server_test.go", + "target": "product/app/server/internal/server_test.go", "blob": "fa287130eb6ebb0e572a68470baae58411efe0bc", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/api.go", - "target": "edge/shared/interface/api/api.go", + "target": "product/app/api/api.go", "blob": "7eca87432217088eef1639f47c513c1bd0341cf0", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/auth_config.go", - "target": "edge/shared/interface/api/auth_config.go", + "target": "product/auth/config/auth_config.go", "blob": "e155092ea9646e7320e1dc3f66b8ae2bc2081409", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/config.go", - "target": "edge/shared/interface/api/config.go", + "target": "product/app/api/config.go", "blob": "1ea1e746f6bd9d28494e9b26d9f97bbdda1f000b", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/register.go", - "target": "edge/shared/interface/api/register.go", + "target": "product/app/api/register.go", "blob": "756ca1e6e3c5d5f0f91184a5013cfb2993990851", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/runtime.go", - "target": "edge/shared/interface/api/runtime.go", + "target": "product/app/api/runtime.go", "blob": "8ab8dc821bcdf1f5d6dfa6183990d30dcbdb63df", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/verification_rate_limit.go", - "target": "edge/shared/interface/api/verification_rate_limit.go", + "target": "product/verification/config/verification_rate_limit.go", "blob": "c1186157f843fe780f6cb54c6c5cf48b1962164b", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/verification_rate_limit_test.go", - "target": "edge/shared/interface/api/verification_rate_limit_test.go", + "target": "product/verification/config/verification_rate_limit_test.go", "blob": "dd85c48ab87e5556f25bb73a0b120f21031dbe4a", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/api/http/methods.go", - "target": "pkg/api/http/methods.go", + "target": "shared/http/methods.go", "blob": "ec73a29c5e7f2d3ade83667ed666a9c15fe6d6e5", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/http/status.go", - "target": "pkg/api/http/status.go", + "target": "shared/http/status.go", "blob": "76f0b7a6e861e1519e7ee018564e5bfadf4be4f6", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/http/response/response.go", - "target": "pkg/api/http/response/response.go", + "target": "shared/http/response/response.go", "blob": "8904d881b718a9ec20d3c4644717118760d8d27e", "adapted_beyond_namespace": true }, { "source": "api/pkg/api/http/response/response_test.go", - "target": "pkg/api/http/response/response_test.go", + "target": "shared/http/response/response_test.go", "blob": "592cbefaeec2bac5bef8d10223e156b6da150a98", "adapted_beyond_namespace": true }, { "source": "api/pkg/api/http/response/result.go", - "target": "pkg/api/http/response/result.go", + "target": "shared/http/response/result.go", "blob": "aa65a480fbf69e3249bb3848f0bc57206c5561bd", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/sresponse/account.go", - "target": "edge/shared/interface/api/sresponse/account.go", + "target": "product/auth/response/account.go", "blob": "0369f802304ab0b19a57349939ac8a73e924322a", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/sresponse/authresp.go", - "target": "edge/shared/interface/api/sresponse/authresp.go", + "target": "product/auth/response/authresp.go", "blob": "c148e9be39be6ecfdae127f8d9980360f0114051", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/sresponse/file.go", - "target": "edge/shared/interface/api/sresponse/file.go", + "target": "product/auth/response/file.go", "blob": "041d7b5d6d56dc059d54bc648b47fe7ba7151f35", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/sresponse/login.go", - "target": "edge/shared/interface/api/sresponse/login.go", + "target": "product/auth/response/login.go", "blob": "962b9ca0852da034c70b9d4174f497b887267ac8", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/sresponse/login_pending.go", - "target": "edge/shared/interface/api/sresponse/login_pending.go", + "target": "product/auth/response/login_pending.go", "blob": "5bcd839cb379c44c4a95d897af4a055b6b9375fc", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/sresponse/login_test.go", - "target": "edge/shared/interface/api/sresponse/login_test.go", + "target": "product/auth/response/login_test.go", "blob": "419995b9a91ebe8db544c7224be6e945de0c5e55", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/sresponse/response.go", - "target": "edge/shared/interface/api/sresponse/response.go", + "target": "product/auth/response/response.go", "blob": "094ec49a7c7ca98f98be42d1946651f666a8287c", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/sresponse/result.go", - "target": "edge/shared/interface/api/sresponse/result.go", + "target": "product/auth/response/result.go", "blob": "64671a5ba2839e8a193a5c199a988401017b78c3", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/sresponse/token.go", - "target": "edge/shared/interface/api/sresponse/token.go", + "target": "product/auth/response/token.go", "blob": "3bbf9df40d9b0e9f7771b851e66d3afe3b71fdb7", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/sresponse/verification.go", - "target": "edge/shared/interface/api/sresponse/verification.go", + "target": "product/auth/response/verification.go", "blob": "bdd15716ff662433625fde76cb27c342509c4a3f", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/model/token.go", - "target": "edge/shared/interface/model/token.go", + "target": "product/auth/model/token.go", "blob": "8f65763eb1924a203417e5cfd85118e039b04502", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/model/token_test.go", - "target": "edge/shared/interface/model/token_test.go", + "target": "product/auth/model/token_test.go", "blob": "9506c15cf24420eb7ca490a5dac5216e43c4e2a8", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/pkg/mutil/duration/duration.go", - "target": "pkg/mutil/duration/duration.go", + "target": "shared/duration/duration.go", "blob": "c27a30425d5988831fcce76a3f13c3647ea0c4e5", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/auth/browser/browser.go", - "target": "edge/shared/auth/browser/browser.go", + "target": "product/sessions/browser/browser.go", "blob": "3af7b54aea277d91e0d8c3eba9ee84c155f4b624", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/auth/browser/browser_test.go", - "target": "edge/shared/auth/browser/browser_test.go", + "target": "product/sessions/browser/browser_test.go", "blob": "d376b2879803f69acd3d52348d259f5ac3629b1b", "adapted_beyond_namespace": false }, { "source": "api/pkg/domainprovider/domain_provider.go", - "target": "pkg/domainprovider/domain_provider.go", + "target": "product/app/urls/domain_provider.go", "blob": "353aac0bf37609cfd0471ce43484ff15baac21f2", "adapted_beyond_namespace": false }, { "source": "api/pkg/domainprovider/imp/domain_provider.go", - "target": "pkg/domainprovider/imp/domain_provider.go", + "target": "product/app/urls/internal/domain_provider.go", "blob": "20d7353be9aa53c9a372fe912af6dca6e78cd709", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/runtime/api.go", - "target": "edge/shared/internal/runtime/api.go", + "target": "product/app/runtime/api.go", "blob": "081cef6ae3887dc2990c5b8d1d10a670893191c6", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/runtime/bootstrap.go", - "target": "edge/shared/internal/runtime/bootstrap.go", + "target": "product/app/runtime/bootstrap.go", "blob": "38108cc6b2f3257e32473877ce3e66fa4be2370c", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/runtime/lifecycle.go", - "target": "edge/shared/internal/runtime/lifecycle.go", + "target": "product/app/runtime/lifecycle.go", "blob": "367e277a21360a21df742c31cb1f5a7712754938", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/runtime/services.go", - "target": "edge/shared/internal/runtime/services.go", + "target": "product/app/runtime/services.go", "blob": "d3fdec1c394925f4790902fa9c18177b713fb66e", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/routers/health/status.go", - "target": "pkg/api/routers/health/status.go", + "target": "product/app/health/status/status.go", "blob": "9768bea4154c61d18712c052730f531c74fc6d53", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/routers/health.go", - "target": "pkg/api/routers/health.go", + "target": "product/app/health/health.go", "blob": "2c8ab2f2f973572f56a5865e849b4226da6ef86b", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/routers/internal/healthimp/health.go", - "target": "pkg/api/routers/internal/healthimp/health.go", + "target": "product/app/health/internal/health.go", "blob": "3b641ce70d370c65809262252847cb1d104fc3bc", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/routers/internal/healthimp/health_test.go", - "target": "pkg/api/routers/internal/healthimp/health_test.go", + "target": "product/app/health/internal/health_test.go", "blob": "5fe00ffd51338f96bf1fbdd9d18c53996f3822ac", "adapted_beyond_namespace": false }, { "source": "api/pkg/api/routers/internal/healthimp/status.go", - "target": "pkg/api/routers/internal/healthimp/status.go", + "target": "product/app/health/internal/status.go", "blob": "af7f1bc54bc3449afdc2a36f61663c9234413f32", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/middleware.go", - "target": "edge/shared/internal/transport/http/middleware.go", + "target": "product/app/http/middleware.go", "blob": "3a4c3f910fe3cbeb3bf6566afa8833dd41b6ad0f", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/middleware_test.go", - "target": "edge/shared/internal/transport/http/middleware_test.go", + "target": "product/app/http/middleware_test.go", "blob": "7359d959b4c29ef95312fb49056351d408fc9957", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/metrics/handler.go", - "target": "edge/shared/internal/transport/http/router/metrics/handler.go", + "target": "product/app/metrics/handler.go", "blob": "fd2e2de1e59c1510ca43b394fa6697497f83b9c9", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/metrics/router.go", - "target": "edge/shared/internal/transport/http/router/metrics/router.go", + "target": "product/app/metrics/router.go", "blob": "8690f1e8f9b39b3957433dd61e887bbd9bf90ef9", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/dispatcher.go", - "target": "edge/shared/internal/transport/http/router/dispatcher.go", + "target": "product/auth/http/dispatcher.go", "blob": "f173a53bab40b61dead720e92fe1cd27f3e5b5a5", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/router.go", - "target": "edge/shared/internal/transport/http/router/router.go", + "target": "product/auth/http/router.go", "blob": "d4e68b80e4b10c8aaf3e10e8e6c163f92595c0de", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/refreshtokens/client.go", - "target": "edge/shared/storage/refreshtokens/client.go", + "target": "product/sessions/store/client.go", "blob": "7289529bdccc68c5678275638e818ebc8a7999e1", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/crud.go", - "target": "edge/shared/storage/refreshtokens/crud.go", + "target": "product/sessions/store/crud.go", "blob": "5f8044ca8c7cd30d107f2272042e4b7b1cfea291", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/crud_test.go", - "target": "edge/shared/storage/refreshtokens/crud_test.go", + "target": "product/sessions/store/crud_test.go", "blob": "385710f18897996b677b0b193c5407f3b78da27d", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/db.go", - "target": "edge/shared/storage/refreshtokens/db.go", + "target": "product/sessions/store/db.go", "blob": "2b2ac2504bac1f239e17105b940b375baa17e3f3", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/fields.go", - "target": "edge/shared/storage/refreshtokens/fields.go", + "target": "product/sessions/store/fields.go", "blob": "ff7ce10d5ed25882a1fc2bf6e964410fa8e4f995", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/refreshtokens/filters.go", - "target": "edge/shared/storage/refreshtokens/filters.go", + "target": "product/sessions/store/filters.go", "blob": "4b51d1c8ee793fdad660d532aad3bccf3c18a167", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/refreshtokens.go", - "target": "edge/shared/storage/refreshtokens/refreshtokens.go", + "target": "product/sessions/store/refreshtokens.go", "blob": "3d335877d4269ef51496f7c139fcc0dd7e7f8dd4", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/refreshtokens_indexes_test.go", - "target": "edge/shared/storage/refreshtokens/refreshtokens_indexes_test.go", + "target": "product/sessions/store/refreshtokens_indexes_test.go", "blob": "d10a740f65ae64adde26d1438d1c2c17bbeaae0f", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/storage/refreshtokens/refreshtokens_misc_test.go", - "target": "edge/shared/storage/refreshtokens/refreshtokens_misc_test.go", + "target": "product/sessions/store/refreshtokens_misc_test.go", "blob": "12ecda38d22b7143910d57889ea8663d9e731958", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/refreshtokens_session_replacement_test.go", - "target": "edge/shared/storage/refreshtokens/refreshtokens_session_replacement_test.go", + "target": "product/sessions/store/refreshtokens_session_replacement_test.go", "blob": "270025162d0902052771251f78f20ddb7f152e5e", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/refreshtokens_session_test.go", - "target": "edge/shared/storage/refreshtokens/refreshtokens_session_test.go", + "target": "product/sessions/store/refreshtokens_session_test.go", "blob": "15dbe4ab226ffcc5b287bdb6d74cc608360195cc", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/refreshtokens_test.go", - "target": "edge/shared/storage/refreshtokens/refreshtokens_test.go", + "target": "product/sessions/store/refreshtokens_test.go", "blob": "e6c398c7db0f603a964eef39160e79e5f9fe2bde", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/storage/refreshtokens/revoke.go", - "target": "edge/shared/storage/refreshtokens/revoke.go", + "target": "product/sessions/store/revoke.go", "blob": "39511ee714bcdb9e9885522517735e596c43dd8a", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/public/login.go", - "target": "edge/shared/internal/transport/http/router/public/login.go", + "target": "product/auth/http/public/login.go", "blob": "92666648e8df7c93fc7ac28a4d0e413f0a370499", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/login_rate_limit.go", - "target": "edge/shared/internal/transport/http/router/public/login_rate_limit.go", + "target": "product/auth/http/public/login_rate_limit.go", "blob": "2122e383a68d56a81aa9ff165dd7661c4bf8068b", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/login_rate_limit_test.go", - "target": "edge/shared/internal/transport/http/router/public/login_rate_limit_test.go", + "target": "product/auth/http/public/login_rate_limit_test.go", "blob": "06d0cf195efa36290c17dd148d6bc2551011664a", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/login_test.go", - "target": "edge/shared/internal/transport/http/router/public/login_test.go", + "target": "product/auth/http/public/login_test.go", "blob": "0fabfa678c8f7df25c7ae744a99ec680668ca899", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/login_test_support_test.go", - "target": "edge/shared/internal/transport/http/router/public/login_test_support_test.go", + "target": "product/auth/http/public/login_test_support_test.go", "blob": "7578548953e01a7cf33e265e4202e6c862ad6bb6", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/logout.go", - "target": "edge/shared/internal/transport/http/router/public/logout.go", + "target": "product/auth/http/public/logout.go", "blob": "0d6d0f1fa27af82c805c28b11d0dcd90462ba34e", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/public/logout_test.go", - "target": "edge/shared/internal/transport/http/router/public/logout_test.go", + "target": "product/auth/http/public/logout_test.go", "blob": "c769afa87c0ffc12213335fe64b98576ff979457", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/refresh.go", - "target": "edge/shared/internal/transport/http/router/public/refresh.go", + "target": "product/auth/http/public/refresh.go", "blob": "88f77303ad8386a42967077b8fbb716de325b4b0", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/public/refresh_test.go", - "target": "edge/shared/internal/transport/http/router/public/refresh_test.go", + "target": "product/auth/http/public/refresh_test.go", "blob": "4f7b9f98a1705414fd1e312629d8ea498c80da64", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/respond.go", - "target": "edge/shared/internal/transport/http/router/public/respond.go", + "target": "product/auth/http/public/respond.go", "blob": "45b8841a9efff51cd8bc6910dcc4f2de8d640d86", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/public/rotate.go", - "target": "edge/shared/internal/transport/http/router/public/rotate.go", + "target": "product/auth/http/public/rotate.go", "blob": "6c052ba5475fce58cbdc0b7827094613d63817a4", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/public/router.go", - "target": "edge/shared/internal/transport/http/router/public/router.go", + "target": "product/auth/http/public/router.go", "blob": "6aacfa1fc8b4874011f90bcd730c16db39933a42", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/public/validate.go", - "target": "edge/shared/internal/transport/http/router/public/validate.go", + "target": "product/auth/http/public/validate.go", "blob": "5a5ea587e1a62eb194e68996e841d3a20cf97fce", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/srequest/login.go", - "target": "edge/shared/interface/api/srequest/login.go", + "target": "product/auth/request/login.go", "blob": "c1f016d26b2cbacfd4352a8a1a2a8d655a0773e5", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/srequest/refresh.go", - "target": "edge/shared/interface/api/srequest/refresh.go", + "target": "product/auth/request/refresh.go", "blob": "f19edff7cc9dd591d09bfd852e4e6137e86daf30", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/srequest/rotate.go", - "target": "edge/shared/interface/api/srequest/rotate.go", + "target": "product/auth/request/rotate.go", "blob": "6315c790396b7201f0b70748087326e6079921c1", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/interface/api/srequest/validateable.go", - "target": "edge/shared/interface/api/srequest/validateable.go", + "target": "product/auth/request/validateable.go", "blob": "b4a5c12f33b1d73690a04732b1562480f544e40a", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/interface/api/srequest/verification.go", - "target": "edge/shared/interface/api/srequest/verification.go", + "target": "product/auth/request/verification.go", "blob": "b3e6cbc77dc6e09feda045477a2f4f750ed37411", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/endpoint/endpoint.go", - "target": "edge/shared/internal/transport/http/router/endpoint/endpoint.go", + "target": "product/auth/http/endpoint/endpoint.go", "blob": "f0254292b49f7e635b231a6c70098c8dcd8a86d8", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/endpoint/install.go", - "target": "edge/shared/internal/transport/http/router/endpoint/install.go", + "target": "product/auth/http/endpoint/install.go", "blob": "e7c1c5dd50b393642f593072ad3484d3c220fee9", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/internal/transport/http/router/endpoint/token.go", - "target": "edge/shared/internal/transport/http/router/endpoint/token.go", + "target": "product/auth/http/endpoint/token.go", "blob": "332924534a7b5fca9a142eaaa08f01098c529905", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/auth/tokens/prepare_test.go", - "target": "edge/shared/auth/tokens/prepare_test.go", + "target": "product/sessions/tokens/prepare_test.go", "blob": "6e8892c9492c4dbc781fc6fb76e3e5aa27158f7a", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/auth/tokens/tokens.go", - "target": "edge/shared/auth/tokens/tokens.go", + "target": "product/sessions/tokens/tokens.go", "blob": "233d14be95e9560697709d03c8c3ec65f381a0aa", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/http/ipguard/ipguard.go", - "target": "edge/shared/http/ipguard/ipguard.go", + "target": "product/auth/ipguard/ipguard.go", "blob": "6a4221d73346135468bba15bdfe8194e70b6d0be", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/http/ipguard/ipguard_test.go", - "target": "edge/shared/http/ipguard/ipguard_test.go", + "target": "product/auth/ipguard/ipguard_test.go", "blob": "a2b7f31d4803844eead0a5fb517ef40e994f33c1", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/auth/sessionmetrics/metrics.go", - "target": "edge/shared/auth/sessionmetrics/metrics.go", + "target": "product/sessions/metrics/metrics.go", "blob": "8b7917de210337c696af4eedc8eaec05597cff96", "adapted_beyond_namespace": false }, { "source": "api/edge/shared/auth/client/client.go", - "target": "edge/shared/auth/client/client.go", + "target": "product/auth/client/client.go", "blob": "4424ce799dda3c4945fbeeaa79cbc21c1d7afefe", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/auth/client/client_test.go", - "target": "edge/shared/auth/client/client_test.go", + "target": "product/auth/client/client_test.go", "blob": "1a429187940a658e9cc63c830281f973fa93d084", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/authorized/handler.go", - "target": "edge/shared/internal/transport/http/router/authorized/handler.go", + "target": "product/auth/http/authorized/handler.go", "blob": "bb2fcde2e850dd687e78cad5f4dc95f9b9417ac6", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/authorized/router.go", - "target": "edge/shared/internal/transport/http/router/authorized/router.go", + "target": "product/auth/http/authorized/router.go", "blob": "4347dd32db99ae4e46ded03bdff2e64a3a049515", "adapted_beyond_namespace": true }, { "source": "api/edge/shared/internal/transport/http/router/authorized/session_test.go", - "target": "edge/shared/internal/transport/http/router/authorized/session_test.go", + "target": "product/auth/http/authorized/session_test.go", "blob": "e81e9f40101bf43f55a5772215557802e15e9b1a", - "adapted_beyond_namespace": false + "adapted_beyond_namespace": true }, { "source": "api/edge/shared/auth/storage/stores.go", - "target": "edge/shared/auth/storage/stores.go", + "target": "product/auth/storage/stores.go", "blob": "7c71f8e9a5216b40865388eb4a10900c0e9b4435", - "adapted_beyond_namespace": false - }, - { - "source": "docs/openapi/api.yaml", - "target": "contracts/sendico/api.yaml", - "blob": "c37aeba487b3eef2bedf5829a89fd3b666fe7944" - }, - { - "source": "docs/openapi/api/ledger_accounts/operation.yaml", - "target": "contracts/sendico/api/ledger_accounts/operation.yaml", - "blob": "06fe2c531824155fbb6ccc2a6bd13279c63daf06" - }, - { - "source": "docs/openapi/api/response/operation.yaml", - "target": "contracts/sendico/api/response/operation.yaml", - "blob": "08c5a5f57094642d9093f8ede394d06b08f03852" - }, - { - "source": "docs/openapi/api/response/response.yaml", - "target": "contracts/sendico/api/response/response.yaml", - "blob": "89589e58af97d3c02b21ec6fab61bc692b9c2bc6" - }, - { - "source": "docs/openapi/api/response/error.yaml", - "target": "contracts/sendico/api/response/error.yaml", - "blob": "c4a70141e44ad8040844d5fa35959b718cf99cce" - }, - { - "source": "docs/openapi/api/response/status.yaml", - "target": "contracts/sendico/api/response/status.yaml", - "blob": "1a7e12360568806a679bf2b7c1457d4000dbacc4" - }, - { - "source": "docs/openapi/api/ledger_accounts/response/operation.yaml", - "target": "contracts/sendico/api/ledger_accounts/response/operation.yaml", - "blob": "44929324586f15c6a1af87e9d6776aa406f2613d" - }, - { - "source": "docs/openapi/models/ledger/operation.yaml", - "target": "contracts/sendico/models/ledger/operation.yaml", - "blob": "60cb2bbc8e398268bec3c28619421d88cdbede41" - }, - { - "source": "docs/openapi/models/objectid.yaml", - "target": "contracts/sendico/models/objectid.yaml", - "blob": "01f1c3ddf61d0364983261ba106dc1b22da1125f" - }, - { - "source": "docs/openapi/models/common/money.yaml", - "target": "contracts/sendico/models/common/money.yaml", - "blob": "b130b4c94cc5789c9721092eb17f8bc9d42f99e1" - }, - { - "source": "docs/openapi/models/auth/token_data.yaml", - "target": "contracts/sendico/models/auth/token_data.yaml", - "blob": "5e6264d6ba053516a57298bb88ef1dd90a272b55" - }, - { - "source": "docs/openapi/api/parameters/entry_ref.yaml", - "target": "contracts/sendico/api/parameters/entry_ref.yaml", - "blob": "d035050889fc20d67f812315ad8e0493703d2d38" - }, - { - "source": "docs/openapi/api/parameters/ledger_account_ref.yaml", - "target": "contracts/sendico/api/parameters/ledger_account_ref.yaml", - "blob": "ac2315052ba8c2013860966a50e46c9aa9f81571" - }, - { - "source": "docs/openapi/api/parameters/organizations_ref.yaml", - "target": "contracts/sendico/api/parameters/organizations_ref.yaml", - "blob": "668d32230b1faf86826a3f46675c6e809781fd2f" - }, - { - "source": "docs/openapi/api/ledger_accounts/operations.yaml", - "target": "contracts/sendico/api/ledger_accounts/operations.yaml", - "blob": "2910a7ba27a90f23d613934163170f13a7a4dc28" - }, - { - "source": "docs/openapi/api/parameters/offset.yaml", - "target": "contracts/sendico/api/parameters/offset.yaml", - "blob": "03b8b7cbf07b533f578f62e5feba678c1fb47658" - }, - { - "source": "docs/openapi/api/parameters/limit.yaml", - "target": "contracts/sendico/api/parameters/limit.yaml", - "blob": "71fa974fe385c691c0ac7113f4bf2dd3f959046b" - }, - { - "source": "docs/openapi/api/ledger_accounts/balance.yaml", - "target": "contracts/sendico/api/ledger_accounts/balance.yaml", - "blob": "75156517b30344caf969e90b830cb886812c0bd3" - }, - { - "source": "docs/openapi/api/ledger_accounts/response/balance.yaml", - "target": "contracts/sendico/api/ledger_accounts/response/balance.yaml", - "blob": "254864b08634dbd503823682cc9df51c89a53a0d" - }, - { - "source": "docs/openapi/models/ledger/balance.yaml", - "target": "contracts/sendico/models/ledger/balance.yaml", - "blob": "df4423b667c6c3e053a781ebbeaaf803b179d3e9" - }, - { - "source": "docs/openapi/api/wallets/assets.yaml", - "target": "contracts/sendico/api/wallets/assets.yaml", - "blob": "17c8b22aae5226042eca93f9d0a4a7ff25859fec" - }, - { - "source": "docs/openapi/api/wallets/response/wallet.yaml", - "target": "contracts/sendico/api/wallets/response/wallet.yaml", - "blob": "b390b85084e3129714e10c37db9e5db6ddd41222" - }, - { - "source": "docs/openapi/models/wallet/wallet_asset.yaml", - "target": "contracts/sendico/models/wallet/wallet_asset.yaml", - "blob": "6d1a1a6686ea06d52717a7d9551a358a12eb4a89" - }, - { - "source": "docs/openapi/external/chain_network.yaml", - "target": "contracts/sendico/external/chain_network.yaml", - "blob": "c5770115083e47b26e2119f0ac301dcf88e40be2" - }, - { - "source": "docs/openapi/api/payments/fx_directions.yaml", - "target": "contracts/sendico/api/payments/fx_directions.yaml", - "blob": "264f35dc443b35f28587892a673519119c580ea3" - }, - { - "source": "docs/openapi/api/payments/response/payment.yaml", - "target": "contracts/sendico/api/payments/response/payment.yaml", - "blob": "d4c2938f87dc8ce39a8575a05f7912242679faee" - }, - { - "source": "docs/openapi/models/payment/fx_metadata.yaml", - "target": "contracts/sendico/models/payment/fx_metadata.yaml", - "blob": "857a2a1d84f41534c563d9090998ffe00b9a5c68" - }, - { - "source": "docs/openapi/external/fx_side.yaml", - "target": "contracts/sendico/external/fx_side.yaml", - "blob": "639e6f28c673cdee0f2e6405bf6304603a33d23a" - }, - { - "source": "docs/openapi/models/payment/payment.yaml", - "target": "contracts/sendico/models/payment/payment.yaml", - "blob": "e970feb77567efb87e18b20bfc4607b128032287" - }, - { - "source": "docs/openapi/external/payment_state.yaml", - "target": "contracts/sendico/external/payment_state.yaml", - "blob": "9685cd8ecb25a0ac700d0baa009a4ce427063957" - }, - { - "source": "docs/openapi/external/settlement_mode.yaml", - "target": "contracts/sendico/external/settlement_mode.yaml", - "blob": "e5531c92b60b923ada5893670a20a0fc3c98038a" - }, - { - "source": "docs/openapi/external/payment_kind.yaml", - "target": "contracts/sendico/external/payment_kind.yaml", - "blob": "06f68ef2f705b59d01dc6cfee55039a550693d9f" - }, - { - "source": "docs/openapi/external/endpoint_type.yaml", - "target": "contracts/sendico/external/endpoint_type.yaml", - "blob": "3a23e427a02288ad1cde4ec94e150b36bb3bb32b" - }, - { - "source": "docs/openapi/models/common/pagination.yaml", - "target": "contracts/sendico/models/common/pagination.yaml", - "blob": "2a923c3317d227c27c337635c93edc2818b0b8df" - }, - { - "source": "docs/openapi/api/payments/fx_currencies.yaml", - "target": "contracts/sendico/api/payments/fx_currencies.yaml", - "blob": "f2ecf5a89513a81458fb658295d9dc2fd7a9bf05" - }, - { - "source": "docs/openapi/api/payments/registry_refresh.yaml", - "target": "contracts/sendico/api/payments/registry_refresh.yaml", - "blob": "4719af7000f06f8012ce3ded03026b8d6d7391d2" - }, - { - "source": "docs/openapi/api/payments/registry.yaml", - "target": "contracts/sendico/api/payments/registry.yaml", - "blob": "42d796d7dea7482f1739302724292e3c7f6d3c5c" - }, - { - "source": "docs/openapi/api/payments/documents_certificate.yaml", - "target": "contracts/sendico/api/payments/documents_certificate.yaml", - "blob": "e7a33787537ef0762eb33f8e9d6065d93ce5dc93" - }, - { - "source": "docs/openapi/api/payments/list.yaml", - "target": "contracts/sendico/api/payments/list.yaml", - "blob": "e01a5c9170b14478cb8cfde8547ddc893646b425" - }, - { - "source": "docs/openapi/api/parameters/filter_states.yaml", - "target": "contracts/sendico/api/parameters/filter_states.yaml", - "blob": "1c1d34fd3ca5de628935c57a3c0db98a356ee07a" - }, - { - "source": "docs/openapi/api/parameters/states.yaml", - "target": "contracts/sendico/api/parameters/states.yaml", - "blob": "96d672d3e56877b8d3f107daaa181f48a792da25" - }, - { - "source": "docs/openapi/api/parameters/state.yaml", - "target": "contracts/sendico/api/parameters/state.yaml", - "blob": "cb05776de5b2325066e3751c9ed3b6ad94984251" - }, - { - "source": "docs/openapi/api/parameters/cursor.yaml", - "target": "contracts/sendico/api/parameters/cursor.yaml", - "blob": "15252a1a96874d58a5fe25eac85dfe537bcf31ec" - }, - { - "source": "docs/openapi/api/payments/object.yaml", - "target": "contracts/sendico/api/payments/object.yaml", - "blob": "0b717523ee8cfa4f4ed9ef77b58ba8c2f4b75f07" - }, - { - "source": "docs/openapi/api/parameters/payments_ref.yaml", - "target": "contracts/sendico/api/parameters/payments_ref.yaml", - "blob": "28a015bff27618e53d0dfb90bcaccd79337fcce8" - }, - { - "source": "docs/openapi/api/payments/by_multiquote.yaml", - "target": "contracts/sendico/api/payments/by_multiquote.yaml", - "blob": "2c41a746d935a191eda71c597b15c633a4ea3c76" - }, - { - "source": "docs/openapi/api/payments/bodies/payment.yaml", - "target": "contracts/sendico/api/payments/bodies/payment.yaml", - "blob": "4dc0349c2c62a140b575245289bd318b9755fcd8" - }, - { - "source": "docs/openapi/api/payments/request/payment.yaml", - "target": "contracts/sendico/api/payments/request/payment.yaml", - "blob": "3c425e3b458975922c1b0efa421c6ba77fdfa75d" - }, - { - "source": "docs/openapi/api/payments/by_quote.yaml", - "target": "contracts/sendico/api/payments/by_quote.yaml", - "blob": "703a98895e13fa79066326e4ebba06bb054711ec" - }, - { - "source": "docs/openapi/api/payments/immediate.yaml", - "target": "contracts/sendico/api/payments/immediate.yaml", - "blob": "2a7b885724484cb99cef21a7ca588392b045d244" - }, - { - "source": "docs/openapi/api/payments/multiquote.yaml", - "target": "contracts/sendico/api/payments/multiquote.yaml", - "blob": "3ac6728ac3d3d2c8ca38cc255eb5f26670af23f5" - }, - { - "source": "docs/openapi/api/payments/quote.yaml", - "target": "contracts/sendico/api/payments/quote.yaml", - "blob": "d40955416dfcf1b06d0277a6edaa31520cad3548" - }, - { - "source": "docs/openapi/api/callbacks/rotate_secret.yaml", - "target": "contracts/sendico/api/callbacks/rotate_secret.yaml", - "blob": "6296d25d6e621c522b5160ca88bd6de599f8da98" - }, - { - "source": "docs/openapi/api/callbacks/response/callback.yaml", - "target": "contracts/sendico/api/callbacks/response/callback.yaml", - "blob": "02e262ba392578a54190f9b6b4195d5af0513a85" - }, - { - "source": "docs/openapi/models/callback/callback.yaml", - "target": "contracts/sendico/models/callback/callback.yaml", - "blob": "f555a058f230548d1b7db7f518ae76fba86e580d" - }, - { - "source": "docs/openapi/models/common/describable.yaml", - "target": "contracts/sendico/models/common/describable.yaml", - "blob": "1bf6d5b7a7122e8253175e686c06893c2296598a" - }, - { - "source": "docs/openapi/models/permission_bound.yaml", - "target": "contracts/sendico/models/permission_bound.yaml", - "blob": "c574a13346031788c0b4f05a242f48ada59636a7" - }, - { - "source": "docs/openapi/models/storable.yaml", - "target": "contracts/sendico/models/storable.yaml", - "blob": "325b291481fea9a4c4e3f7c2b39a24156881fc99" - }, - { - "source": "docs/openapi/api/parameters/callbacks_ref.yaml", - "target": "contracts/sendico/api/parameters/callbacks_ref.yaml", - "blob": "18ba8143d1ac91846c3378d51eab5fa3dd703027", - "adaptation": "Trailing blank lines normalized; schema unchanged." - }, - { - "source": "docs/openapi/api/callbacks/archive.yaml", - "target": "contracts/sendico/api/callbacks/archive.yaml", - "blob": "38d8b53ef33cf61aee5b4cd83c6e6910d98ba6dd", - "adaptation": "Trailing blank lines normalized; schema unchanged." - }, - { - "source": "docs/openapi/api/parameters/cascade.yaml", - "target": "contracts/sendico/api/parameters/cascade.yaml", - "blob": "4077d851c091da108603209a8427a836c3617ad6" - }, - { - "source": "docs/openapi/api/parameters/org_ref.yaml", - "target": "contracts/sendico/api/parameters/org_ref.yaml", - "blob": "3ef0c64a6e6f959c0a1bad51317531b1a0595b26" - }, - { - "source": "docs/openapi/api/callbacks/update.yaml", - "target": "contracts/sendico/api/callbacks/update.yaml", - "blob": "e3f99320074f081c26ab930a077319ae3d47e160" - }, - { - "source": "docs/openapi/api/callbacks/bodies/callback.yaml", - "target": "contracts/sendico/api/callbacks/bodies/callback.yaml", - "blob": "850efd327b75c821ab07894cb545658b3bbd60af" - }, - { - "source": "docs/openapi/api/callbacks/request/callback.yaml", - "target": "contracts/sendico/api/callbacks/request/callback.yaml", - "blob": "a0535c32c7ded53d36c4e57cfe45e4d560d9176e" - }, - { - "source": "docs/openapi/api/callbacks/object.yaml", - "target": "contracts/sendico/api/callbacks/object.yaml", - "blob": "914b00db31432a82e57e512554882cc21647aa28", - "adaptation": "Trailing blank lines normalized; schema unchanged." - }, - { - "source": "docs/openapi/api/callbacks/create.yaml", - "target": "contracts/sendico/api/callbacks/create.yaml", - "blob": "92c5c32552ea7471a3dd19e38be1a496644ab33f" - }, - { - "source": "docs/openapi/api/callbacks/list.yaml", - "target": "contracts/sendico/api/callbacks/list.yaml", - "blob": "0072f453ea152f275f7c84ce88989206b93480ca" - }, - { - "source": "docs/openapi/api/parameters/archived.yaml", - "target": "contracts/sendico/api/parameters/archived.yaml", - "blob": "fb8af8ca3f0ed5fbdf0df29a67c8d4e95e9ea2f4" - }, - { - "source": "docs/openapi/api/payment_methods/archive.yaml", - "target": "contracts/sendico/api/payment_methods/archive.yaml", - "blob": "55772206b43a4f653dab9184ef295c5d62542747" - }, - { - "source": "docs/openapi/api/payment_methods/response/payment_method.yaml", - "target": "contracts/sendico/api/payment_methods/response/payment_method.yaml", - "blob": "40cb7aaa4a8b76d91e3e50b78f448ab8edae95fc" - }, - { - "source": "docs/openapi/models/payment_method/payment_method.yaml", - "target": "contracts/sendico/models/payment_method/payment_method.yaml", - "blob": "7b475f009573c91d23c2eb1c052f0c598d2b83fb" - }, - { - "source": "docs/openapi/external/payment_method_type.yaml", - "target": "contracts/sendico/external/payment_method_type.yaml", - "blob": "c8c4e84b005b24df1e83f3f985e041c8f88b9b70" - }, - { - "source": "docs/openapi/api/parameters/payment_methods_ref.yaml", - "target": "contracts/sendico/api/parameters/payment_methods_ref.yaml", - "blob": "6690d53707229be9a3c94adcd924038901a8364c" - }, - { - "source": "docs/openapi/api/payment_methods/object.yaml", - "target": "contracts/sendico/api/payment_methods/object.yaml", - "blob": "a183d571cb4da960a5959429a7c95104d0cbf650" - }, - { - "source": "docs/openapi/api/payment_methods/bodies/payment_method.yaml", - "target": "contracts/sendico/api/payment_methods/bodies/payment_method.yaml", - "blob": "56d488e05949965ffaf5a4d3b4d7c615da683b63" - }, - { - "source": "docs/openapi/api/payment_methods/request/payment_method.yaml", - "target": "contracts/sendico/api/payment_methods/request/payment_method.yaml", - "blob": "6f768e7f08cbed79e86b1dd3f750080ca65dfb8c" - }, - { - "source": "docs/openapi/api/payment_methods/recipients.yaml", - "target": "contracts/sendico/api/payment_methods/recipients.yaml", - "blob": "414ab3fb957e4a3e314331d61692d2e89fcd656b" - }, - { - "source": "docs/openapi/api/parameters/recipients_ref.yaml", - "target": "contracts/sendico/api/parameters/recipients_ref.yaml", - "blob": "945bca14a5caaf09b768ea3688588ec899772d19" - }, - { - "source": "docs/openapi/api/payment_methods/accounts.yaml", - "target": "contracts/sendico/api/payment_methods/accounts.yaml", - "blob": "9c7fc85d69cda0e8956155396dc8b2669588a3c0" - }, - { - "source": "docs/openapi/api/parameters/accounts_ref.yaml", - "target": "contracts/sendico/api/parameters/accounts_ref.yaml", - "blob": "a72a17d1144b1095947280a87e53c5a1b0c03788" - }, - { - "source": "docs/openapi/api/recipients/archive.yaml", - "target": "contracts/sendico/api/recipients/archive.yaml", - "blob": "4d3cb3f37639ea2a9dad5fa3c63b509fcea4edef" - }, - { - "source": "docs/openapi/api/recipients/response/recipient.yaml", - "target": "contracts/sendico/api/recipients/response/recipient.yaml", - "blob": "337b850bd9b2cdd4525326cc6ba3df8309b1fef6" - }, - { - "source": "docs/openapi/models/recipient/recipient.yaml", - "target": "contracts/sendico/models/recipient/recipient.yaml", - "blob": "5ab3d56462523f98b69230836c744437d347dccd" - }, - { - "source": "docs/openapi/api/recipients/update.yaml", - "target": "contracts/sendico/api/recipients/update.yaml", - "blob": "f814f5b2fd485b8d12b6a789780a15183a13fc78" - }, - { - "source": "docs/openapi/api/recipients/bodies/recipient.yaml", - "target": "contracts/sendico/api/recipients/bodies/recipient.yaml", - "blob": "5785002594afd5d7231a268fb4c294016c41492d" - }, - { - "source": "docs/openapi/api/recipients/request/recipient.yaml", - "target": "contracts/sendico/api/recipients/request/recipient.yaml", - "blob": "19656c44ff18c69551275b8c26722b1f2b34a181" - }, - { - "source": "docs/openapi/api/recipients/object.yaml", - "target": "contracts/sendico/api/recipients/object.yaml", - "blob": "091e5c7c5ebec4a29f7bd5e29378750c16d8e834" - }, - { - "source": "docs/openapi/api/recipients/create.yaml", - "target": "contracts/sendico/api/recipients/create.yaml", - "blob": "10fc1ba66b2c7617932e7f25287a34bf576b5694" - }, - { - "source": "docs/openapi/api/recipients/list.yaml", - "target": "contracts/sendico/api/recipients/list.yaml", - "blob": "33ca97f1a2ddd0b392f33a48eb2972eed1e0161d" - }, - { - "source": "docs/openapi/api/organizations/get.yaml", - "target": "contracts/sendico/api/organizations/get.yaml", - "blob": "c93ed7a4d5fecdfe72ebb7cefc2f38aae7f24bf0" - }, - { - "source": "docs/openapi/api/organizations/response/organization.yaml", - "target": "contracts/sendico/api/organizations/response/organization.yaml", - "blob": "56fc0f76a363e6ecf08413ff955f63798b736a84" - }, - { - "source": "docs/openapi/models/organization/organization.yaml", - "target": "contracts/sendico/models/organization/organization.yaml", - "blob": "8b6dec1dba78c16614211b21d574121db48713aa" - }, - { - "source": "docs/openapi/api/organizations/root.yaml", - "target": "contracts/sendico/api/organizations/root.yaml", - "blob": "7a00d19c7b2500c4199d17638b817097a0c4255f" - }, - { - "source": "docs/openapi/api/verification/verify.yaml", - "target": "contracts/sendico/api/verification/verify.yaml", - "blob": "cf26d7b3b26aa5b1dcbe9606cdb7dd1fb49bc94f" - }, - { - "source": "docs/openapi/api/verification/response/verification.yaml", - "target": "contracts/sendico/api/verification/response/verification.yaml", - "blob": "f169ab7b8f843e02e64c07fd3b4bf3f4916fd270" - }, - { - "source": "docs/openapi/api/accounts/response/auth.yaml", - "target": "contracts/sendico/api/accounts/response/auth.yaml", - "blob": "dd3265423b5854b908d17cef482c8d12fab5c958" - }, - { - "source": "docs/openapi/models/account/account.yaml", - "target": "contracts/sendico/models/account/account.yaml", - "blob": "b47ae25d410359cbfb7080560715e8e7389ecbdd" - }, - { - "source": "docs/openapi/models/account/account_public.yaml", - "target": "contracts/sendico/models/account/account_public.yaml", - "blob": "3f807896b73e06d23149ca7bd6d63cada27a042c" - }, - { - "source": "docs/openapi/api/verification/bodies/verification.yaml", - "target": "contracts/sendico/api/verification/bodies/verification.yaml", - "blob": "da9dbd6b43df2daccca60506889f7273bf1a0d99" - }, - { - "source": "docs/openapi/api/verification/request/verification.yaml", - "target": "contracts/sendico/api/verification/request/verification.yaml", - "blob": "a0050984df5818d36da6a4f8c307d9ba6bc915a4" - }, - { - "source": "docs/openapi/models/auth/session_identifier.yaml", - "target": "contracts/sendico/models/auth/session_identifier.yaml", - "blob": "fba55b0e4efe7f9960e97c4fe42cdf29ec3182eb" - }, - { - "source": "docs/openapi/api/verification/resend.yaml", - "target": "contracts/sendico/api/verification/resend.yaml", - "blob": "d2ee0fc195ecdc07196d4c7d777037c7cd42101c" - }, - { - "source": "docs/openapi/api/verification/create.yaml", - "target": "contracts/sendico/api/verification/create.yaml", - "blob": "9dd4e9790c5ca73937a8b7fb543f0c5682484ef6" - }, - { - "source": "docs/openapi/api/accounts/auth_refresh.yaml", - "target": "contracts/sendico/api/accounts/auth_refresh.yaml", - "blob": "2822daaa7b00efd4f688fc1eb21fd3279e386b32" - }, - { - "source": "docs/openapi/api/accounts/bodies/auth.yaml", - "target": "contracts/sendico/api/accounts/bodies/auth.yaml", - "blob": "f7a723d2c8142f9fe7b176c562b33648f2b3654f" - }, - { - "source": "docs/openapi/api/accounts/request/auth.yaml", - "target": "contracts/sendico/api/accounts/request/auth.yaml", - "blob": "b000fd83ab37af6ceb7c5ebe252fc61733ef17d2" - }, - { - "source": "docs/openapi/models/auth/client_refresh_token.yaml", - "target": "contracts/sendico/models/auth/client_refresh_token.yaml", - "blob": "43408e367e8139fb49416038f4de00f9146b108e" - }, - { - "source": "docs/openapi/models/auth/login_data.yaml", - "target": "contracts/sendico/models/auth/login_data.yaml", - "blob": "480b3d202f0d2cf437e83f4210ff21cd42ffbcdd" - }, - { - "source": "docs/openapi/api/accounts/auth_rotate.yaml", - "target": "contracts/sendico/api/accounts/auth_rotate.yaml", - "blob": "9515e4dd178a115b09fe92f9fe28ca1e39b708d6" - }, - { - "source": "docs/openapi/api/accounts/auth_login_api.yaml", - "target": "contracts/sendico/api/accounts/auth_login_api.yaml", - "blob": "3bb72b2e1d257dcc15db1d9f9986ea0c5617d1b8" - }, - { - "source": "docs/openapi/api/accounts/auth_login.yaml", - "target": "contracts/sendico/api/accounts/auth_login.yaml", - "blob": "ede859161d26767acc3e865c040343301e7e6f55" + "adapted_beyond_namespace": true }, { "source": "api/edge/product/main.go", - "target": "cmd/bff/main.go", + "target": "product/main.go", "blob": "4c9f6dfd4436bf75fac15690decdac0efaa5b9c2", "adapted_beyond_namespace": true + }, + { + "source": "api/edge/shared/interface/api/auth_config.go", + "target": "product/verification/config/config.go", + "blob": "e155092ea9646e7320e1dc3f66b8ae2bc2081409", + "adapted_beyond_namespace": true } ], "excluded": [ diff --git a/backend/PROVENANCE.md b/backend/PROVENANCE.md index 5a8e592..b7a71c6 100644 --- a/backend/PROVENANCE.md +++ b/backend/PROVENANCE.md @@ -4,9 +4,9 @@ Source repository: `S_I/sendico` (`git.sendico.io`), revision `8bb20dcd622dd8eb92d289b6b34411e7eccab083`. [PROVENANCE.json](PROVENANCE.json) records each retained file, its original path -and Git blob, and whether a Go file changed beyond namespace replacement and -formatting. It currently covers 232 Go source/test files and 112 public OpenAPI -files. Sources came from the pinned commit, not the dirty Sendico checkout. +and Git blob, and whether a Go file changed beyond import-path replacement and +formatting. It currently covers 233 Go source/test entries across `product` and `shared` +(one original configuration file contributes to two feature packages). Sources came from the pinned commit, not the dirty Sendico checkout. These are SXCHNGE-owned copies that may diverge independently. ## Retained implementation @@ -18,13 +18,11 @@ These are SXCHNGE-owned copies that may diverge independently. - Verification issuance, expiry, attempts, consumption and idempotency. - Local Mongo connection, transactions and the repository/query implementation required by account, session and verification storage, with retained tests. -- Public OpenAPI 4.2.37 and its recursive schema references, isolated under - `contracts/sendico`. These wire files are unchanged except for trailing blank - line normalization in three YAML files; they are not local domain models. - ## Adaptations and exclusions -Imports belong to `git.sendico.io/tech/sxchnge/bff`. The composition root selects +The backend contains Go modules `git.sendico.io/tech/sxchnge/product` and +`git.sendico.io/tech/sxchnge/shared`. Product owns its executable entry point; +shared is a dependency library with no executable or domain-module imports. The composition root selects local customer services. Discovery, message-bus registration, private gRPC and platform factories are absent. Process identity and the refresh cookie use SXCHNGE. Startup validates required settings and timeouts, closes failed connections and @@ -60,3 +58,16 @@ This is a bootstrap. Signup/profile HTTP wiring, verification delivery/completio and the public Sendico HTTP adapter remain explicit work in the [architecture plan](../docs/architecture/bff.md). The sole Sendico organization reference belongs to that adapter's configuration, never to local customers. + +## Module organization + +`backend/` groups modules; it has no root `go.mod`. The inherited `cmd`, `edge`, +`internal`, `pkg` and empty `scripts` top-level directories are gone. `product` +contains `main.go`, its manifest and app/customer/auth/session/verification code. +`shared` owns its manifest and generic infrastructure. Models moved beside their +owning features. Customer repository construction moved out of shared database +code. Auth and verification configurations moved out of application interfaces. + +All 112 unconsumed public API snapshot files and their snapshot-only check were +removed. Future public adapters will own just the wire types they consume. +Backoffice is a separate future executable module, not an alias for product. diff --git a/backend/README.md b/backend/README.md index 0c4373f..2bb03f7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,41 +1,35 @@ -# SXCHNGE BFF +# Go backend modules -A locally owned copy of Sendico's reusable BFF runtime, authentication/session -stack and customer account service. See [provenance](PROVENANCE.md) and the -[architecture and public API plan](../docs/architecture/bff.md). +`backend/` groups independent Go modules. It is not an executable or a Go module. -SXCHNGE owns its customers and authentication. SXCHNGE itself is one organization -in Sendico. Provider integration uses only Sendico's public HTTP API; the pinned -[OpenAPI snapshot](contracts/sendico/api.yaml) is isolated from local models. +```text +backend/ +├── product/ Customer-facing BFF executable +│ ├── main.go +│ ├── go.mod +│ ├── app/ Startup, composition, HTTP server and health +│ ├── customers/ Customer service, models and Mongo repository +│ ├── auth/ Login/protected HTTP routes, DTOs and configuration +│ ├── sessions/ Session models, refresh storage, cookies and tokens +│ └── verification/ Verification models, configuration and storage +└── shared/ Reusable library module + ├── go.mod + ├── database/ Connection, transactions and repository primitives + ├── errors/ + ├── logging/ + └── … Generic HTTP, configuration and rate-limit helpers +``` -## Run locally +The backoffice BFF belongs in a separate `backend/backoffice/` executable module, +with its own `main.go`, `go.mod`, configuration and operator authentication. It is +not implemented by this customer-BFF bootstrap. Shared infrastructure has no +`main.go` and must not depend on an executable module. The product module uses a +local `replace` for `../shared`; each module has its own dependency manifest. -Requires Go 1.27 and an SXCHNGE-owned MongoDB replica set for transactions. -Copy `config.example.yml` to an untracked `config.yml`. Set: +Run the product with `make run-backend`; see its [run guide](product/README.md). +`make test` runs both backend modules and the frontend. `make lint-backend` checks +both Go modules and their architecture boundaries. -- `SXCHNGE_MONGO_URI`: URI for the local SXCHNGE database. -- `SXCHNGE_JWT_SECRET`: private random signing key, at least 32 bytes for HS256. -- `SXCHNGE_DOMAIN`: local API hostname (for example `localhost:8082`). -- `SXCHNGE_API_PROTOCOL`: `http` locally, `https` in deployment. -- `SXCHNGE_API_ENDPOINT`: `/api/v1`. - -The runtime starts against an empty SXCHNGE database and creates its indexes. -No organization, role, policy or seed is required. -Run `make run-backend` from the repository root. The copied CLI accepts -`-config.file`, `-version` and `-debug`. Configure exact browser origins in YAML; -secure refresh cookies require HTTPS for browser session testing. - -The runtime mounts `/api/v1/health` and the copied account login, API login, -refresh, rotation and logout handlers. Customer/client records must exist in the -local database. The account service creates customers and activation tokens -atomically, and deletes customers with session revocation. It is initialized locally; signup/profile and -verification-delivery/completion routes are the next wiring work. This is a -bootstrap, not a finished authentication deployment. No Sendico calls are made. - -## Validate - -Run `make test` and `make lint-backend`. Backend tests include real MongoDB -integration tests through Testcontainers, so Docker must be running. -`make test-backend` runs the backend suite with the race detector. -Architecture checks reject private Sendico imports/transports and ad hoc error -constructors. The frontend still uses dummy services; no Flutter source changed. +Copied source remains locally owned. See [provenance](PROVENANCE.md), the +[architecture/API plan](../docs/architecture/bff.md) and the +[delivery record](../docs/implementation.md). diff --git a/backend/cmd/bff/main.go b/backend/cmd/bff/main.go deleted file mode 100644 index e6095d5..0000000 --- a/backend/cmd/bff/main.go +++ /dev/null @@ -1,21 +0,0 @@ -// Adapted from Sendico api/edge/product/main.go; see PROVENANCE.md. -package main - -import ( - "git.sendico.io/tech/sxchnge/bff/edge/shared/appversion" - "git.sendico.io/tech/sxchnge/bff/edge/shared/server" - "git.sendico.io/tech/sxchnge/bff/internal/profile" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - application "git.sendico.io/tech/sxchnge/bff/pkg/server" - smain "git.sendico.io/tech/sxchnge/bff/pkg/server/main" - "os" -) - -func factory(logger mlogger.Logger, file string, debug bool) (application.Application, error) { - return server.Create(logger, file, debug, profile.Runtime()) -} -func main() { - if smain.RunServer("sxchnge.bff", appversion.Create(), factory) != nil { - os.Exit(1) - } -} diff --git a/backend/contracts/sendico/api.yaml b/backend/contracts/sendico/api.yaml deleted file mode 100644 index c37aeba..0000000 --- a/backend/contracts/sendico/api.yaml +++ /dev/null @@ -1,129 +0,0 @@ -openapi: 3.1.0 -info: - title: Sendico API Contract - version: 4.2.37 - summary: Minimal client contract for authenticated BFF flows. - description: | - OpenAPI 3.1 contract focused on authenticated client flows exposed by the BFF: - authentication, verification, recipients, payment methods, payments, wallets, - and ledger account history. -servers: - - url: /api/v1 - description: Main HTTP API base path - -tags: - - name: Auth - description: Authentication and token lifecycle - - name: Accounts - description: Account auth responses used by login/verification - - name: Verification - description: Pending-account code verification - - name: Organizations - description: Organization selection for payment operations - - name: Recipients - description: Recipient CRUD and archive flows - - name: Payment Methods - description: Payment method CRUD and archive flows - - name: Callbacks - description: Webhook callback subscription CRUD and signing material rotation for HMAC and detached-JWS callbacks - - name: Payments - description: Quotation and payment orchestration - - name: Wallets - description: Managed wallet metadata and balances - - name: Ledger Accounts - description: Ledger account balances and account-history operations - -paths: - /accounts/login: - $ref: ./api/accounts/auth_login.yaml - /accounts/login/api: - $ref: ./api/accounts/auth_login_api.yaml - /accounts/rotate: - $ref: ./api/accounts/auth_rotate.yaml - /accounts/refresh: - $ref: ./api/accounts/auth_refresh.yaml - - /verification: - $ref: ./api/verification/create.yaml - /verification/resend: - $ref: ./api/verification/resend.yaml - /verification/verify: - $ref: ./api/verification/verify.yaml - - /organizations: - $ref: ./api/organizations/root.yaml - /organizations/{org_ref}: - $ref: ./api/organizations/get.yaml - - /recipients/list/{org_ref}/{organizations_ref}: - $ref: ./api/recipients/list.yaml - /recipients/{org_ref}: - $ref: ./api/recipients/create.yaml - /recipients/{recipients_ref}: - $ref: ./api/recipients/object.yaml - /recipients: - $ref: ./api/recipients/update.yaml - /recipients/archive/{org_ref}/{recipients_ref}: - $ref: ./api/recipients/archive.yaml - - /payment_methods/accounts/{organizations_ref}/{accounts_ref}: - $ref: ./api/payment_methods/accounts.yaml - /payment_methods/recipients/{organizations_ref}/{recipients_ref}: - $ref: ./api/payment_methods/recipients.yaml - /payment_methods/{organizations_ref}/{payment_methods_ref}: - $ref: ./api/payment_methods/object.yaml - /payment_methods/archive/{organizations_ref}/{payment_methods_ref}: - $ref: ./api/payment_methods/archive.yaml - - /callbacks/list/{org_ref}/{organizations_ref}: - $ref: ./api/callbacks/list.yaml - /callbacks/{org_ref}: - $ref: ./api/callbacks/create.yaml - /callbacks/{callbacks_ref}: - $ref: ./api/callbacks/object.yaml - /callbacks: - $ref: ./api/callbacks/update.yaml - /callbacks/archive/{org_ref}/{callbacks_ref}: - $ref: ./api/callbacks/archive.yaml - /callbacks/rotate-secret/{callbacks_ref}: - $ref: ./api/callbacks/rotate_secret.yaml - - /payments/quote/{organizations_ref}: - $ref: ./api/payments/quote.yaml - /payments/multiquote/{organizations_ref}: - $ref: ./api/payments/multiquote.yaml - /payments/immediate/{organizations_ref}: - $ref: ./api/payments/immediate.yaml - /payments/by-quote/{organizations_ref}: - $ref: ./api/payments/by_quote.yaml - /payments/by-multiquote/{organizations_ref}: - $ref: ./api/payments/by_multiquote.yaml - /payments/by-ref/{payments_ref}: - $ref: ./api/payments/object.yaml - /payments/{organizations_ref}: - $ref: ./api/payments/list.yaml - /payments/documents/certificate/{organizations_ref}: - $ref: ./api/payments/documents_certificate.yaml - /payments/registry/{organizations_ref}: - $ref: ./api/payments/registry.yaml - /payments/registry/refresh/{organizations_ref}: - $ref: ./api/payments/registry_refresh.yaml - /payments/fx/currencies/{organizations_ref}: - $ref: ./api/payments/fx_currencies.yaml - /payments/fx/directions/{organizations_ref}: - $ref: ./api/payments/fx_directions.yaml - /chain_wallets/{organizations_ref}/assets: - $ref: ./api/wallets/assets.yaml - /ledger_accounts/{organizations_ref}/{ledger_account_ref}/balance: - $ref: ./api/ledger_accounts/balance.yaml - /ledger_accounts/{organizations_ref}/{ledger_account_ref}/operations: - $ref: ./api/ledger_accounts/operations.yaml - /ledger_accounts/{organizations_ref}/{ledger_account_ref}/operations/{entry_ref}: - $ref: ./api/ledger_accounts/operation.yaml - -components: - securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT diff --git a/backend/contracts/sendico/api/accounts/auth_login.yaml b/backend/contracts/sendico/api/accounts/auth_login.yaml deleted file mode 100644 index ede8591..0000000 --- a/backend/contracts/sendico/api/accounts/auth_login.yaml +++ /dev/null @@ -1,29 +0,0 @@ -post: - tags: [Accounts, Auth] - summary: Login using email/password and receive pending verification token - description: | - Validates account credentials and returns a short-lived `pendingToken`. - This endpoint does not return final access/refresh tokens; complete verification flow via `/verification/verify`. - operationId: accountsLogin - requestBody: - $ref: ./bodies/auth.yaml#/components/requestBodies/LoginBody - responses: - '202': - description: Pending verification token issued - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/auth.yaml#/components/schemas/PendingLoginData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/accounts/auth_login_api.yaml b/backend/contracts/sendico/api/accounts/auth_login_api.yaml deleted file mode 100644 index 3bb72b2..0000000 --- a/backend/contracts/sendico/api/accounts/auth_login_api.yaml +++ /dev/null @@ -1,30 +0,0 @@ -post: - tags: [Accounts, Auth] - summary: API login using email/password and client credentials - description: | - Validates account credentials and eligible API client credentials, then returns final auth payload. - This endpoint bypasses login OTP/2FA and is intended only for approved API clients. - If the client definition includes `allowedCIDRs`, request source IP must match one of those CIDRs. - operationId: accountsApiLogin - requestBody: - $ref: ./bodies/auth.yaml#/components/requestBodies/ApiLoginBody - responses: - '200': - description: Login successful - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/auth.yaml#/components/schemas/LoginData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/accounts/auth_refresh.yaml b/backend/contracts/sendico/api/accounts/auth_refresh.yaml deleted file mode 100644 index 2822daa..0000000 --- a/backend/contracts/sendico/api/accounts/auth_refresh.yaml +++ /dev/null @@ -1,31 +0,0 @@ -post: - tags: [Accounts, Auth] - summary: Refresh access token by refresh token - description: | - Validates refresh token + session identifiers and issues a new access token. - Use this endpoint when refresh token stays the same. - operationId: accountsRefreshAccessToken - requestBody: - $ref: ./bodies/auth.yaml#/components/requestBodies/RefreshTokenBody - responses: - '200': - description: Access token refreshed - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/auth.yaml#/components/schemas/AccountAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/accounts/auth_rotate.yaml b/backend/contracts/sendico/api/accounts/auth_rotate.yaml deleted file mode 100644 index 9515e4d..0000000 --- a/backend/contracts/sendico/api/accounts/auth_rotate.yaml +++ /dev/null @@ -1,31 +0,0 @@ -post: - tags: [Accounts, Auth] - summary: Rotate refresh token and issue new access/refresh tokens - description: | - Validates refresh token + session identifiers and returns a new token pair. - Use this endpoint when replacing both access and refresh tokens. - operationId: accountsRotateRefreshToken - requestBody: - $ref: ./bodies/auth.yaml#/components/requestBodies/RefreshTokenBody - responses: - '200': - description: New token pair issued - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/auth.yaml#/components/schemas/LoginData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/accounts/bodies/auth.yaml b/backend/contracts/sendico/api/accounts/bodies/auth.yaml deleted file mode 100644 index f7a723d..0000000 --- a/backend/contracts/sendico/api/accounts/bodies/auth.yaml +++ /dev/null @@ -1,24 +0,0 @@ -components: - requestBodies: - LoginBody: - description: JSON credentials payload for standard login. - required: true - content: - application/json: - schema: - $ref: ../request/auth.yaml#/components/schemas/LoginRequest - ApiLoginBody: - description: JSON credentials payload for API client login. - required: true - content: - application/json: - schema: - $ref: ../request/auth.yaml#/components/schemas/ApiLoginRequest - - RefreshTokenBody: - description: JSON payload containing session and refresh token data. - required: true - content: - application/json: - schema: - $ref: ../request/auth.yaml#/components/schemas/RefreshTokenRequest diff --git a/backend/contracts/sendico/api/accounts/request/auth.yaml b/backend/contracts/sendico/api/accounts/request/auth.yaml deleted file mode 100644 index b000fd8..0000000 --- a/backend/contracts/sendico/api/accounts/request/auth.yaml +++ /dev/null @@ -1,34 +0,0 @@ -components: - schemas: - LoginRequest: - type: object - additionalProperties: false - required: - - login - properties: - clientId: - type: string - description: Client identifier bound to refresh token lifecycle and client policy checks. - deviceId: - type: string - description: Device/server identifier associated with the client session. - login: - $ref: ../../../models/auth/login_data.yaml#/components/schemas/LoginData - - ApiLoginRequest: - allOf: - - $ref: ./auth.yaml#/components/schemas/LoginRequest - - type: object - additionalProperties: false - required: - - clientId - - deviceId - - clientSecret - properties: - clientSecret: - type: string - format: password - description: Client secret for `client_secret_post` authentication. - - RefreshTokenRequest: - $ref: ../../../models/auth/client_refresh_token.yaml#/components/schemas/ClientRefreshToken diff --git a/backend/contracts/sendico/api/accounts/response/auth.yaml b/backend/contracts/sendico/api/accounts/response/auth.yaml deleted file mode 100644 index dd32654..0000000 --- a/backend/contracts/sendico/api/accounts/response/auth.yaml +++ /dev/null @@ -1,62 +0,0 @@ -components: - schemas: - AccountAuthData: - description: Authentication response containing account profile and access token. - type: object - additionalProperties: false - required: - - accessToken - - account - properties: - accessToken: - description: Access token used for authenticated requests. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - account: - description: Authenticated account data. - $ref: ../../../models/account/account.yaml#/components/schemas/AccountData - - LoginData: - description: Full login response including access and refresh tokens. - type: object - additionalProperties: false - required: - - accessToken - - account - - refreshToken - properties: - accessToken: - description: Access token used for authenticated requests. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - account: - description: Authenticated account data. - $ref: ../../../models/account/account.yaml#/components/schemas/AccountData - refreshToken: - description: Refresh token used to obtain a new access token. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - - PendingLoginData: - description: Pending login response requiring additional verification. - type: object - additionalProperties: false - required: - - account - - pendingToken - - target - properties: - account: - description: Interim authentication payload prepared for verification completion. - type: object - additionalProperties: false - properties: - accessToken: - description: Temporary access token issued before verification is completed. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - account: - description: Account data associated with the pending login flow. - $ref: ../../../models/account/account.yaml#/components/schemas/AccountData - pendingToken: - description: Token proving the pending verification session. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - target: - description: Verification target destination (for example email or phone). - type: string diff --git a/backend/contracts/sendico/api/callbacks/archive.yaml b/backend/contracts/sendico/api/callbacks/archive.yaml deleted file mode 100644 index dfc1cde..0000000 --- a/backend/contracts/sendico/api/callbacks/archive.yaml +++ /dev/null @@ -1,37 +0,0 @@ -get: - tags: [Callbacks] - summary: Archive/unarchive callback subscription - description: Sets callback archive state by `callbacks_ref` and required `archived` query parameter. - operationId: callbacksArchive - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - - $ref: ../parameters/callbacks_ref.yaml#/components/parameters/CallbacksRef - - name: archived - in: query - required: true - description: Target archive value to set on the callback. - schema: - type: boolean - - $ref: ../parameters/cascade.yaml#/components/parameters/Cascade - responses: - '200': - description: Archive state updated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/callbacks/bodies/callback.yaml b/backend/contracts/sendico/api/callbacks/bodies/callback.yaml deleted file mode 100644 index 850efd3..0000000 --- a/backend/contracts/sendico/api/callbacks/bodies/callback.yaml +++ /dev/null @@ -1,17 +0,0 @@ -components: - requestBodies: - CallbackCreateBody: - description: JSON body containing callback endpoint configuration for create. - required: true - content: - application/json: - schema: - $ref: ../request/callback.yaml#/components/schemas/CallbackCreateRequest - - CallbackUpdateBody: - description: JSON body containing callback endpoint configuration for update. - required: true - content: - application/json: - schema: - $ref: ../request/callback.yaml#/components/schemas/CallbackUpdateRequest diff --git a/backend/contracts/sendico/api/callbacks/create.yaml b/backend/contracts/sendico/api/callbacks/create.yaml deleted file mode 100644 index 92c5c32..0000000 --- a/backend/contracts/sendico/api/callbacks/create.yaml +++ /dev/null @@ -1,33 +0,0 @@ -post: - tags: [Callbacks] - summary: Create callback subscription - description: Creates callback subscription for the organization identified by `org_ref`. - operationId: callbacksCreate - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - requestBody: - $ref: ./bodies/callback.yaml#/components/requestBodies/CallbackCreateBody - responses: - '201': - description: Callback created - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/callbacks/list.yaml b/backend/contracts/sendico/api/callbacks/list.yaml deleted file mode 100644 index 0072f45..0000000 --- a/backend/contracts/sendico/api/callbacks/list.yaml +++ /dev/null @@ -1,35 +0,0 @@ -get: - tags: [Callbacks] - summary: List callback subscriptions - description: | - Lists callbacks for the given organization context. - The route currently includes both `org_ref` and `organizations_ref`; the handler resolves organization context from `org_ref` and ignores `organizations_ref`. - operationId: callbacksList - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/limit.yaml#/components/parameters/Limit - - $ref: ../parameters/offset.yaml#/components/parameters/Offset - - $ref: ../parameters/archived.yaml#/components/parameters/Archived - responses: - '200': - description: Callback list - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/callbacks/object.yaml b/backend/contracts/sendico/api/callbacks/object.yaml deleted file mode 100644 index e585c74..0000000 --- a/backend/contracts/sendico/api/callbacks/object.yaml +++ /dev/null @@ -1,64 +0,0 @@ -get: - tags: [Callbacks] - summary: Get callback subscription - description: Returns callback subscription by `callbacks_ref`. - operationId: callbacksGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/callbacks_ref.yaml#/components/parameters/CallbacksRef - responses: - '200': - description: Callback data - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - -delete: - tags: [Callbacks] - summary: Delete callback subscription - description: Deletes callback by reference. - operationId: callbacksDelete - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/callbacks_ref.yaml#/components/parameters/CallbacksRef - - $ref: ../parameters/cascade.yaml#/components/parameters/Cascade - responses: - '200': - description: Callback deleted - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/callbacks/request/callback.yaml b/backend/contracts/sendico/api/callbacks/request/callback.yaml deleted file mode 100644 index a0535c3..0000000 --- a/backend/contracts/sendico/api/callbacks/request/callback.yaml +++ /dev/null @@ -1,113 +0,0 @@ -components: - schemas: - CallbackBackoffInput: - description: Optional retry backoff overrides. Non-positive values currently fall back to server defaults. - type: object - additionalProperties: false - properties: - minDelayMs: - type: integer - maxDelayMs: - type: integer - - CallbackRetryPolicyInput: - description: Optional retry and signing overrides. Omitted or non-positive numeric values currently fall back to server defaults. - type: object - additionalProperties: false - properties: - backoff: - $ref: '#/components/schemas/CallbackBackoffInput' - signingMode: - description: | - Optional signing mode. Canonical values are documented here; current handler also accepts legacy aliases such as `hmac`, `hmac-sha256`, `jws-eddsa-ed25519`, `jws+eddsa-ed25519`, `jws_eddsa`, and `eddsa_ed25519`. - type: string - enum: - - none - - hmac_sha256 - - jws_eddsa_ed25519 - headers: - type: object - additionalProperties: - type: string - maxAttempts: - type: integer - requestTimeoutMs: - type: integer - - CallbackCreateRequest: - description: | - Request payload used to create a callback configuration. - Only `url` is hard-required by current create logic. Other mutable fields are optional and defaulted by the handler when omitted. - type: object - additionalProperties: false - required: - - url - properties: - name: - description: Optional callback display name. When omitted, the handler defaults it to `url`. - type: string - description: - description: Optional callback description. - type: string - nullable: true - status: - description: | - Optional callback status. Canonical values are documented here; current handler also accepts `enabled` and `inactive` aliases. - type: string - enum: - - active - - disabled - url: - description: Absolute HTTPS callback endpoint URL. - type: string - format: uri - eventTypes: - description: Optional callback event types. When omitted or empty, the handler defaults this list. - type: array - items: - type: string - retryPolicy: - $ref: '#/components/schemas/CallbackRetryPolicyInput' - - CallbackUpdateRequest: - description: | - Request payload used to update a callback configuration. - The current PUT handler requires `id`, `organizationRef`, and `url`. Other mutable fields are optional, but omitted values may be reset to handler defaults during normalization. - type: object - additionalProperties: false - required: - - id - - organizationRef - - url - properties: - id: - description: Callback identifier to update. - $ref: ../../../models/objectid.yaml#/components/schemas/ObjectId - organizationRef: - description: Organization identifier carried in the stored callback record and currently required by the update path. - $ref: ../../../models/objectid.yaml#/components/schemas/ObjectId - name: - description: Optional callback display name. When omitted, the handler defaults it to `url`. - type: string - description: - description: Optional callback description. - type: string - nullable: true - status: - description: | - Optional callback status. Canonical values are documented here; current handler also accepts `enabled` and `inactive` aliases. - type: string - enum: - - active - - disabled - url: - description: Absolute HTTPS callback endpoint URL. - type: string - format: uri - eventTypes: - description: Optional callback event types. When omitted or empty, the handler defaults this list. - type: array - items: - type: string - retryPolicy: - $ref: '#/components/schemas/CallbackRetryPolicyInput' diff --git a/backend/contracts/sendico/api/callbacks/response/callback.yaml b/backend/contracts/sendico/api/callbacks/response/callback.yaml deleted file mode 100644 index 02e262b..0000000 --- a/backend/contracts/sendico/api/callbacks/response/callback.yaml +++ /dev/null @@ -1,22 +0,0 @@ -components: - schemas: - CallbacksAuthData: - description: Authenticated response payload containing callback configurations. - type: object - additionalProperties: false - required: - - accessToken - - callbacks - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - callbacks: - description: Collection of callbacks configured for the authenticated scope. JWS-signed callbacks expose read-only verification JWKs via `callbacks[].signingKeys`. - type: array - items: - $ref: ../../../models/callback/callback.yaml#/components/schemas/Callback - generatedSigningSecret: - description: Newly generated signing secret when HMAC signing material is created or rotated. JWS-signed callbacks do not populate this field. - type: string - nullable: true diff --git a/backend/contracts/sendico/api/callbacks/rotate_secret.yaml b/backend/contracts/sendico/api/callbacks/rotate_secret.yaml deleted file mode 100644 index 6296d25..0000000 --- a/backend/contracts/sendico/api/callbacks/rotate_secret.yaml +++ /dev/null @@ -1,31 +0,0 @@ -post: - tags: [Callbacks] - summary: Rotate callback signing material - description: Rotates the active signing material for the callback. HMAC callbacks generate and return a new shared secret once. `jws_eddsa_ed25519` callbacks generate a new Ed25519 keypair, expose the new active verification JWK on normal callback reads, and keep retiring verification keys visible for 24 hours. - operationId: callbacksRotateSecret - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/callbacks_ref.yaml#/components/parameters/CallbacksRef - responses: - '200': - description: Callback signing material rotated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/callbacks/update.yaml b/backend/contracts/sendico/api/callbacks/update.yaml deleted file mode 100644 index e3f9932..0000000 --- a/backend/contracts/sendico/api/callbacks/update.yaml +++ /dev/null @@ -1,31 +0,0 @@ -put: - tags: [Callbacks] - summary: Update callback subscription - description: Updates callback subscription fields. The current handler requires `id`, `organizationRef`, and `url`; omitted mutable fields may be reset to defaults. - operationId: callbacksUpdate - security: - - bearerAuth: [] - requestBody: - $ref: ./bodies/callback.yaml#/components/requestBodies/CallbackUpdateBody - responses: - '200': - description: Callback updated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/callback.yaml#/components/schemas/CallbacksAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/ledger_accounts/balance.yaml b/backend/contracts/sendico/api/ledger_accounts/balance.yaml deleted file mode 100644 index 7515651..0000000 --- a/backend/contracts/sendico/api/ledger_accounts/balance.yaml +++ /dev/null @@ -1,58 +0,0 @@ -get: - tags: [Ledger Accounts] - summary: Get ledger account balance - description: | - Returns the canonical balance for the requested ledger account. - - Without `as_of`, the response is the current materialized balance and includes - current metadata fields `version` and `lastUpdated`. - - With `as_of`, the response is an event-time accounting balance reconstructed - from posted statement-entry deltas where `eventTime <= as_of`. Entries exactly - equal to `as_of` are included. This is not a historical snapshot of materialized - balance state and not "what the system knew" at that clock time. Late or - backdated postings with older `eventTime` can change historical responses for - the same `as_of`. Historical responses include `ledgerAccountRef`, `balance`, - and canonical UTC `asOf`, and omit `version` and `lastUpdated` because no - historical snapshot/version semantics are defined. - - Historical balance is valid only if every account-affecting committed posting - writes matching statement entries in the same transaction as balance updates. - operationId: ledgerAccountBalanceGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/ledger_account_ref.yaml#/components/parameters/LedgerAccountRef - - name: as_of - in: query - required: false - description: | - Optional RFC3339 event-time cutoff for historical balance reconstruction. - Timezone offsets are accepted and returned as canonical UTC `asOf`. - schema: - type: string - format: date-time - example: '2026-04-23T14:30:00+02:00' - responses: - '200': - description: Ledger account balance - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/balance.yaml#/components/schemas/LedgerAccountBalanceData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/ledger_accounts/operation.yaml b/backend/contracts/sendico/api/ledger_accounts/operation.yaml deleted file mode 100644 index 06fe2c5..0000000 --- a/backend/contracts/sendico/api/ledger_accounts/operation.yaml +++ /dev/null @@ -1,36 +0,0 @@ -get: - tags: [Ledger Accounts] - summary: Get ledger account operation detail - description: | - Returns the account-history projection for `entry_ref` within the requested ledger account. - This is not a general journal-entry read API. - The endpoint returns `404` when the entry is not part of the requested account's visible history. - operationId: ledgerAccountOperationsGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/ledger_account_ref.yaml#/components/parameters/LedgerAccountRef - - $ref: ../parameters/entry_ref.yaml#/components/parameters/EntryRef - responses: - '200': - description: Ledger account operation detail - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/operation.yaml#/components/schemas/LedgerAccountOperationData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/ledger_accounts/operations.yaml b/backend/contracts/sendico/api/ledger_accounts/operations.yaml deleted file mode 100644 index 2910a7b..0000000 --- a/backend/contracts/sendico/api/ledger_accounts/operations.yaml +++ /dev/null @@ -1,36 +0,0 @@ -get: - tags: [Ledger Accounts] - summary: List ledger account operations - description: | - Returns account-history summary projections for the requested ledger account. - Public HTTP pagination supports only `limit` and `offset`. - Results are ordered by `version` descending and `entry_ref` descending. - Unsupported query parameters, including `archived`, are rejected. - operationId: ledgerAccountOperationsList - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/ledger_account_ref.yaml#/components/parameters/LedgerAccountRef - - $ref: ../parameters/limit.yaml#/components/parameters/Limit - - $ref: ../parameters/offset.yaml#/components/parameters/Offset - responses: - '200': - description: Ledger account operation summaries - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/operation.yaml#/components/schemas/LedgerAccountOperationsData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/ledger_accounts/response/balance.yaml b/backend/contracts/sendico/api/ledger_accounts/response/balance.yaml deleted file mode 100644 index 254864b..0000000 --- a/backend/contracts/sendico/api/ledger_accounts/response/balance.yaml +++ /dev/null @@ -1,15 +0,0 @@ -components: - schemas: - LedgerAccountBalanceData: - description: Response payload containing a ledger account balance. - type: object - additionalProperties: false - required: - - accessToken - - balance - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - balance: - $ref: ../../../models/ledger/balance.yaml#/components/schemas/LedgerAccountBalance diff --git a/backend/contracts/sendico/api/ledger_accounts/response/operation.yaml b/backend/contracts/sendico/api/ledger_accounts/response/operation.yaml deleted file mode 100644 index 4492932..0000000 --- a/backend/contracts/sendico/api/ledger_accounts/response/operation.yaml +++ /dev/null @@ -1,33 +0,0 @@ -components: - schemas: - LedgerAccountOperationsData: - description: Response payload containing ledger account history summaries. - type: object - additionalProperties: false - required: - - accessToken - - operations - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - operations: - description: Account-history summaries ordered by `version DESC, entryRef DESC`. - type: array - items: - $ref: ../../../models/ledger/operation.yaml#/components/schemas/LedgerAccountOperationSummary - - LedgerAccountOperationData: - description: Response payload containing a single ledger account history projection. - type: object - additionalProperties: false - required: - - accessToken - - operation - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - operation: - description: Account-history projection for the requested entry and ledger account. - $ref: ../../../models/ledger/operation.yaml#/components/schemas/LedgerAccountOperation diff --git a/backend/contracts/sendico/api/organizations/get.yaml b/backend/contracts/sendico/api/organizations/get.yaml deleted file mode 100644 index c93ed7a..0000000 --- a/backend/contracts/sendico/api/organizations/get.yaml +++ /dev/null @@ -1,29 +0,0 @@ -get: - tags: [Organizations] - summary: Get organization by reference - description: Fetches a single organization available to the authenticated account. - operationId: organizationsGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - responses: - '200': - description: Organization details - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/organization.yaml#/components/schemas/OrganizationsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/organizations/response/organization.yaml b/backend/contracts/sendico/api/organizations/response/organization.yaml deleted file mode 100644 index 56fc0f7..0000000 --- a/backend/contracts/sendico/api/organizations/response/organization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -components: - schemas: - OrganizationsAuthData: - description: Authenticated response payload containing organizations. - type: object - additionalProperties: false - required: - - accessToken - - organizations - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - organizations: - description: Collection of organizations available to the authenticated account. - type: array - items: - $ref: ../../../models/organization/organization.yaml#/components/schemas/Organization diff --git a/backend/contracts/sendico/api/organizations/root.yaml b/backend/contracts/sendico/api/organizations/root.yaml deleted file mode 100644 index 7a00d19..0000000 --- a/backend/contracts/sendico/api/organizations/root.yaml +++ /dev/null @@ -1,27 +0,0 @@ -get: - tags: [Organizations] - summary: List organizations of current account - description: Returns organizations the authenticated account can access. - operationId: organizationsList - security: - - bearerAuth: [] - responses: - '200': - description: Organizations list - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/organization.yaml#/components/schemas/OrganizationsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/parameters/accounts_ref.yaml b/backend/contracts/sendico/api/parameters/accounts_ref.yaml deleted file mode 100644 index a72a17d..0000000 --- a/backend/contracts/sendico/api/parameters/accounts_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - AccountsRef: - name: accounts_ref - in: path - required: true - description: Account reference, 24 hex chars. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/archived.yaml b/backend/contracts/sendico/api/parameters/archived.yaml deleted file mode 100644 index fb8af8c..0000000 --- a/backend/contracts/sendico/api/parameters/archived.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - Archived: - name: archived - in: query - required: false - description: Filter by archive flag. `true` returns archived records, `false` returns active records. - schema: - type: boolean diff --git a/backend/contracts/sendico/api/parameters/callbacks_ref.yaml b/backend/contracts/sendico/api/parameters/callbacks_ref.yaml deleted file mode 100644 index c12cefa..0000000 --- a/backend/contracts/sendico/api/parameters/callbacks_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - CallbacksRef: - name: callbacks_ref - in: path - required: true - description: Callback subscription reference, 24 hex chars. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/cascade.yaml b/backend/contracts/sendico/api/parameters/cascade.yaml deleted file mode 100644 index 4077d85..0000000 --- a/backend/contracts/sendico/api/parameters/cascade.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - Cascade: - name: cascade - in: query - required: false - description: If `true`, apply operation to dependent objects where cascade is supported. - schema: - type: boolean diff --git a/backend/contracts/sendico/api/parameters/cursor.yaml b/backend/contracts/sendico/api/parameters/cursor.yaml deleted file mode 100644 index 15252a1..0000000 --- a/backend/contracts/sendico/api/parameters/cursor.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - Cursor: - name: cursor - in: query - required: false - description: Opaque cursor from a previous page response (`page.next_cursor`). - schema: - type: string diff --git a/backend/contracts/sendico/api/parameters/entry_ref.yaml b/backend/contracts/sendico/api/parameters/entry_ref.yaml deleted file mode 100644 index d035050..0000000 --- a/backend/contracts/sendico/api/parameters/entry_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - EntryRef: - name: entry_ref - in: path - required: true - description: Statement entry reference within the requested ledger account history. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/filter_states.yaml b/backend/contracts/sendico/api/parameters/filter_states.yaml deleted file mode 100644 index 1c1d34f..0000000 --- a/backend/contracts/sendico/api/parameters/filter_states.yaml +++ /dev/null @@ -1,11 +0,0 @@ -components: - parameters: - FilterStates: - name: filter_states - in: query - required: false - description: | - Additional payment state filter. Accepts one value or comma-separated values. - Same semantics as `state`/`states`. - schema: - type: string diff --git a/backend/contracts/sendico/api/parameters/ledger_account_ref.yaml b/backend/contracts/sendico/api/parameters/ledger_account_ref.yaml deleted file mode 100644 index ac23150..0000000 --- a/backend/contracts/sendico/api/parameters/ledger_account_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - LedgerAccountRef: - name: ledger_account_ref - in: path - required: true - description: Ledger account reference that scopes the account-history read. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/limit.yaml b/backend/contracts/sendico/api/parameters/limit.yaml deleted file mode 100644 index 71fa974..0000000 --- a/backend/contracts/sendico/api/parameters/limit.yaml +++ /dev/null @@ -1,11 +0,0 @@ -components: - parameters: - Limit: - name: limit - in: query - description: Max number of items to return. Parsed as non-negative int64. - required: false - schema: - type: integer - format: int64 - minimum: 0 diff --git a/backend/contracts/sendico/api/parameters/offset.yaml b/backend/contracts/sendico/api/parameters/offset.yaml deleted file mode 100644 index 03b8b7c..0000000 --- a/backend/contracts/sendico/api/parameters/offset.yaml +++ /dev/null @@ -1,11 +0,0 @@ -components: - parameters: - Offset: - name: offset - in: query - description: Number of items to skip. Parsed as non-negative int64. - required: false - schema: - type: integer - format: int64 - minimum: 0 diff --git a/backend/contracts/sendico/api/parameters/org_ref.yaml b/backend/contracts/sendico/api/parameters/org_ref.yaml deleted file mode 100644 index 3ef0c64..0000000 --- a/backend/contracts/sendico/api/parameters/org_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - OrgRef: - name: org_ref - in: path - required: true - description: Organization reference in BFF route format, 24 hex chars. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/organizations_ref.yaml b/backend/contracts/sendico/api/parameters/organizations_ref.yaml deleted file mode 100644 index 668d322..0000000 --- a/backend/contracts/sendico/api/parameters/organizations_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - OrganizationsRef: - name: organizations_ref - in: path - required: true - description: Organization reference used by payments/payment-methods/recipients routes. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/payment_methods_ref.yaml b/backend/contracts/sendico/api/parameters/payment_methods_ref.yaml deleted file mode 100644 index 6690d53..0000000 --- a/backend/contracts/sendico/api/parameters/payment_methods_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - Payment MethodsRef: - name: payment_methods_ref - in: path - required: true - description: Payment method reference, 24 hex chars. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/payments_ref.yaml b/backend/contracts/sendico/api/parameters/payments_ref.yaml deleted file mode 100644 index 28a015b..0000000 --- a/backend/contracts/sendico/api/parameters/payments_ref.yaml +++ /dev/null @@ -1,10 +0,0 @@ -components: - parameters: - PaymentsRef: - name: payments_ref - in: path - required: true - description: Stable payment reference. - schema: - type: string - minLength: 1 diff --git a/backend/contracts/sendico/api/parameters/recipients_ref.yaml b/backend/contracts/sendico/api/parameters/recipients_ref.yaml deleted file mode 100644 index 945bca1..0000000 --- a/backend/contracts/sendico/api/parameters/recipients_ref.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - parameters: - RecipientsRef: - name: recipients_ref - in: path - required: true - description: Recipient reference, 24 hex chars. - schema: - $ref: ../../models/objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/api/parameters/state.yaml b/backend/contracts/sendico/api/parameters/state.yaml deleted file mode 100644 index cb05776..0000000 --- a/backend/contracts/sendico/api/parameters/state.yaml +++ /dev/null @@ -1,11 +0,0 @@ -components: - parameters: - State: - name: state - in: query - required: false - description: | - Payment state filter. Accepts aliases like `accepted`, `settled`, `failed` - or canonical values like `orchestration_state_created`. - schema: - type: string diff --git a/backend/contracts/sendico/api/parameters/states.yaml b/backend/contracts/sendico/api/parameters/states.yaml deleted file mode 100644 index 96d672d..0000000 --- a/backend/contracts/sendico/api/parameters/states.yaml +++ /dev/null @@ -1,10 +0,0 @@ -components: - parameters: - States: - name: states - in: query - required: false - description: | - Multi-state filter. Provide a comma-separated list with same accepted values as `state`. - schema: - type: string diff --git a/backend/contracts/sendico/api/payment_methods/accounts.yaml b/backend/contracts/sendico/api/payment_methods/accounts.yaml deleted file mode 100644 index 9c7fc85..0000000 --- a/backend/contracts/sendico/api/payment_methods/accounts.yaml +++ /dev/null @@ -1,74 +0,0 @@ -get: - tags: [Payment Methods] - summary: List account-owned payment methods - description: | - Returns payment methods owned by `accounts_ref` within `organizations_ref`. - Supports offset pagination and optional archive filtering. - operationId: paymentMethodsListAccountOwned - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/accounts_ref.yaml#/components/parameters/AccountsRef - - $ref: ../parameters/limit.yaml#/components/parameters/Limit - - $ref: ../parameters/offset.yaml#/components/parameters/Offset - - $ref: ../parameters/archived.yaml#/components/parameters/Archived - responses: - '200': - description: Payment methods list - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - -post: - tags: [Payment Methods] - summary: Create account-owned payment method - description: Creates a payment method owned by `accounts_ref` inside `organizations_ref`. - operationId: paymentMethodsCreateAccountOwned - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/accounts_ref.yaml#/components/parameters/AccountsRef - requestBody: - $ref: ./bodies/payment_method.yaml#/components/requestBodies/PaymentMethodCreateBody - responses: - '201': - description: Payment method created - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payment_methods/archive.yaml b/backend/contracts/sendico/api/payment_methods/archive.yaml deleted file mode 100644 index 5577220..0000000 --- a/backend/contracts/sendico/api/payment_methods/archive.yaml +++ /dev/null @@ -1,36 +0,0 @@ -post: - tags: [Payment Methods] - summary: Archive/unarchive payment method - description: Sets payment method archive state by `payment_methods_ref` and required `archived` query parameter. - operationId: paymentMethodsArchive - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/payment_methods_ref.yaml#/components/parameters/Payment MethodsRef - - name: archived - in: query - required: true - description: Target archive value to set on the payment method. - schema: - type: boolean - responses: - '200': - description: Archive state updated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payment_methods/bodies/payment_method.yaml b/backend/contracts/sendico/api/payment_methods/bodies/payment_method.yaml deleted file mode 100644 index 56d488e..0000000 --- a/backend/contracts/sendico/api/payment_methods/bodies/payment_method.yaml +++ /dev/null @@ -1,17 +0,0 @@ -components: - requestBodies: - PaymentMethodCreateBody: - description: JSON body containing a payment method definition for create. - required: true - content: - application/json: - schema: - $ref: ../request/payment_method.yaml#/components/schemas/PaymentMethodCreateRequest - - PaymentMethodUpdateBody: - description: JSON body containing a payment method definition for update. - required: true - content: - application/json: - schema: - $ref: ../request/payment_method.yaml#/components/schemas/PaymentMethodUpdateRequest diff --git a/backend/contracts/sendico/api/payment_methods/object.yaml b/backend/contracts/sendico/api/payment_methods/object.yaml deleted file mode 100644 index a183d57..0000000 --- a/backend/contracts/sendico/api/payment_methods/object.yaml +++ /dev/null @@ -1,102 +0,0 @@ -get: - tags: [Payment Methods] - summary: Get payment method - description: Returns payment method by `payment_methods_ref` inside `organizations_ref`. - operationId: paymentMethodsGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/payment_methods_ref.yaml#/components/parameters/Payment MethodsRef - responses: - '200': - description: Payment method data - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - -patch: - tags: [Payment Methods] - summary: Update payment method - description: Updates mutable payment method fields for the record identified by `payment_methods_ref` inside `organizations_ref`. - operationId: paymentMethodsUpdate - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/payment_methods_ref.yaml#/components/parameters/Payment MethodsRef - requestBody: - $ref: ./bodies/payment_method.yaml#/components/requestBodies/PaymentMethodUpdateBody - responses: - '200': - description: Payment method updated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - -delete: - tags: [Payment Methods] - summary: Delete payment method - description: Deletes payment method by reference. - operationId: paymentMethodsDelete - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/payment_methods_ref.yaml#/components/parameters/Payment MethodsRef - responses: - '200': - description: Payment method deleted - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payment_methods/recipients.yaml b/backend/contracts/sendico/api/payment_methods/recipients.yaml deleted file mode 100644 index 414ab3f..0000000 --- a/backend/contracts/sendico/api/payment_methods/recipients.yaml +++ /dev/null @@ -1,74 +0,0 @@ -get: - tags: [Payment Methods] - summary: List recipient-owned payment methods - description: | - Returns payment methods owned by `recipients_ref` within `organizations_ref`. - Supports offset pagination and optional archive filtering. - operationId: paymentMethodsListRecipientOwned - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/recipients_ref.yaml#/components/parameters/RecipientsRef - - $ref: ../parameters/limit.yaml#/components/parameters/Limit - - $ref: ../parameters/offset.yaml#/components/parameters/Offset - - $ref: ../parameters/archived.yaml#/components/parameters/Archived - responses: - '200': - description: Payment methods list - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - -post: - tags: [Payment Methods] - summary: Create recipient-owned payment method - description: Creates a payment method owned by `recipients_ref` inside `organizations_ref`. - operationId: paymentMethodsCreateRecipientOwned - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/recipients_ref.yaml#/components/parameters/RecipientsRef - requestBody: - $ref: ./bodies/payment_method.yaml#/components/requestBodies/PaymentMethodCreateBody - responses: - '201': - description: Payment method created - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment_method.yaml#/components/schemas/Payment MethodsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payment_methods/request/payment_method.yaml b/backend/contracts/sendico/api/payment_methods/request/payment_method.yaml deleted file mode 100644 index 6f768e7..0000000 --- a/backend/contracts/sendico/api/payment_methods/request/payment_method.yaml +++ /dev/null @@ -1,42 +0,0 @@ -components: - schemas: - PaymentMethodBaseRequest: - type: object - additionalProperties: false - properties: - name: - description: Optional human-readable payment method name. - type: string - description: - description: Optional free-form payment method description. - type: string - nullable: true - type: - description: Payment method type discriminator. - $ref: ../../../external/payment_method_type.yaml#/components/schemas/PaymentMethodType - data: - description: | - Optional payment-method-type-specific payload. - For `type: bankAccount`, this uses the Russian bank-account - destination shape documented as `BankAccountEndpoint`. - type: object - additionalProperties: true - isMain: - description: Optional primary-method flag. When omitted, current handler behavior falls back to `false`. - type: boolean - - PaymentMethodCreateRequest: - description: | - Request payload used to create a payment method. - Ownership comes from the route path. The body only carries mutable payment-method fields. - required: - - type - allOf: - - $ref: '#/components/schemas/PaymentMethodBaseRequest' - - PaymentMethodUpdateRequest: - description: | - Request payload used to update a payment method. - This is a PATCH contract. Only `name`, `description`, `data`, and `isMain` are mutable. - allOf: - - $ref: '#/components/schemas/PaymentMethodBaseRequest' diff --git a/backend/contracts/sendico/api/payment_methods/response/payment_method.yaml b/backend/contracts/sendico/api/payment_methods/response/payment_method.yaml deleted file mode 100644 index 40cb7aa..0000000 --- a/backend/contracts/sendico/api/payment_methods/response/payment_method.yaml +++ /dev/null @@ -1,18 +0,0 @@ -components: - schemas: - Payment MethodsAuthData: - description: Authenticated response payload containing payment methods. - type: object - additionalProperties: false - required: - - accessToken - - payment_methods - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - payment_methods: - description: Collection of payment methods available to the authenticated account. - type: array - items: - $ref: ../../../models/payment_method/payment_method.yaml#/components/schemas/PaymentMethod diff --git a/backend/contracts/sendico/api/payments/bodies/payment.yaml b/backend/contracts/sendico/api/payments/bodies/payment.yaml deleted file mode 100644 index 4dc0349..0000000 --- a/backend/contracts/sendico/api/payments/bodies/payment.yaml +++ /dev/null @@ -1,33 +0,0 @@ -components: - requestBodies: - QuotePaymentBody: - description: JSON payload used to request a quote for one payment intent. - required: true - content: - application/json: - schema: - $ref: ../request/payment.yaml#/components/schemas/QuotePaymentRequest - - QuotePaymentsBody: - description: JSON payload used to request quotes for multiple payment intents. - required: true - content: - application/json: - schema: - $ref: ../request/payment.yaml#/components/schemas/QuotePaymentsRequest - - InitiatePaymentBody: - description: JSON payload used to initiate a single payment. - required: true - content: - application/json: - schema: - $ref: ../request/payment.yaml#/components/schemas/InitiatePaymentRequest - - InitiatePaymentsBody: - description: JSON payload used to initiate multiple payments from a quote reference. - required: true - content: - application/json: - schema: - $ref: ../request/payment.yaml#/components/schemas/InitiatePaymentsRequest diff --git a/backend/contracts/sendico/api/payments/by_multiquote.yaml b/backend/contracts/sendico/api/payments/by_multiquote.yaml deleted file mode 100644 index 2c41a74..0000000 --- a/backend/contracts/sendico/api/payments/by_multiquote.yaml +++ /dev/null @@ -1,37 +0,0 @@ -post: - tags: [Payments] - summary: Initiate payment batch by quote reference - description: | - Executes batch payment from a previously created multi-quote reference. If any quoted item - uses a raw `card` destination, that item's original quote must already include `exp_month` - and `exp_year`. Card-recipient AML screening occurs during execution, not quotation. An - unchanged payment item reuses its persisted compliance decision on idempotent execution - replay, avoiding a duplicate AML call. A definitive denial terminates that payment with - public `failureCode: failure_restriction` and `failureReason: aml_denied`. - operationId: paymentsInitiateBatchByQuote - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - requestBody: - $ref: ./bodies/payment.yaml#/components/requestBodies/InitiatePaymentsBody - responses: - '200': - description: Payment batch initiated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentsData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/by_quote.yaml b/backend/contracts/sendico/api/payments/by_quote.yaml deleted file mode 100644 index 703a988..0000000 --- a/backend/contracts/sendico/api/payments/by_quote.yaml +++ /dev/null @@ -1,41 +0,0 @@ -post: - tags: [Payments] - summary: Initiate payment using quote reference - description: | - Executes one payment from an existing quote reference. If the quoted destination is raw - `card`, the original quote must already include `exp_month` and `exp_year`. Card-recipient - AML screening occurs during execution, not quotation. An unchanged payment reuses its - persisted compliance decision on idempotent execution replay, avoiding a duplicate AML call. - A definitive denial terminates the payment with public `failureCode: failure_restriction` - and `failureReason: aml_denied`. - operationId: paymentsInitiateByQuote - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - requestBody: - required: true - content: - application/json: - schema: - $ref: ./request/payment.yaml#/components/schemas/InitiatePaymentByQuoteRequest - responses: - '200': - description: Payment initiated by quote - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/documents_certificate.yaml b/backend/contracts/sendico/api/payments/documents_certificate.yaml deleted file mode 100644 index e7a3378..0000000 --- a/backend/contracts/sendico/api/payments/documents_certificate.yaml +++ /dev/null @@ -1,47 +0,0 @@ -get: - tags: [Payments] - summary: Download payment certificate - description: | - Returns a generated payment certificate as binary content. - The payment must be settled; the BFF resolves certificate details from the payment reference. - operationId: paymentsGetPaymentCertificate - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - name: payment_ref - in: query - required: true - description: Settled payment reference used to generate the payment certificate. - schema: - type: string - responses: - '200': - description: Payment certificate file - content: - application/pdf: - schema: - type: string - format: binary - application/octet-stream: - schema: - type: string - format: binary - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - '503': - description: Upstream service unavailable - content: - application/json: - schema: - $ref: ../response/response.yaml#/components/schemas/ErrorResponse diff --git a/backend/contracts/sendico/api/payments/fx_currencies.yaml b/backend/contracts/sendico/api/payments/fx_currencies.yaml deleted file mode 100644 index f2ecf5a..0000000 --- a/backend/contracts/sendico/api/payments/fx_currencies.yaml +++ /dev/null @@ -1,40 +0,0 @@ -get: - tags: [Payments] - summary: List available FX currencies - description: | - Returns the BFF startup-cached Treasury Currency Catalog projection for - client-visible FX currencies. `decimals`, `scale`, and `displayScale` are - presentation fields backed by Treasury `display_scale`. - `accountingScaleCandidate` is informational only and must not be used by - clients to determine executable payout precision, quote precision, or - guaranteed provider support. - The endpoint is authenticated and authorized by `organizations_ref`, but the - returned catalogue is global rather than organization-customized. If Treasury - was unavailable at BFF startup while this endpoint was enabled, BFF fails - startup; if Treasury becomes unavailable after startup, BFF continues serving - the cached catalog revision. - operationId: paymentsFXCurrencies - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - responses: - '200': - description: FX currency catalogue projected from Treasury - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/FXCurrenciesData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '503': - $ref: ../response/operation.yaml#/components/responses/ServiceUnavailable diff --git a/backend/contracts/sendico/api/payments/fx_directions.yaml b/backend/contracts/sendico/api/payments/fx_directions.yaml deleted file mode 100644 index 264f35d..0000000 --- a/backend/contracts/sendico/api/payments/fx_directions.yaml +++ /dev/null @@ -1,48 +0,0 @@ -get: - tags: [Payments] - summary: List available FX directions - description: | - Returns organization-level FX direction availability. - - A returned direction means the organization has at least one eligible - direction-level route. It does not mean that every quote or payment for this - direction will be accepted. The actual route, gateway, limits, pricing, fees, - asset domain, and settlement details are resolved during quotation/payment - execution. - - If dependencies are healthy but no organization-eligible executable - directions match oracle quoteable pair sides, the response contains an empty - `directions` array. Oracle or gateway/route registry dependency failures are - returned as errors and are not converted to oracle-only or empty successful - responses. - operationId: paymentsFXDirections - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - responses: - '200': - description: Organization-level directed FX availability - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/FXDirectionsData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '412': - description: Dependency precondition failed - content: - application/json: - schema: - $ref: ../response/response.yaml#/components/schemas/ErrorResponse - '503': - $ref: ../response/operation.yaml#/components/responses/ServiceUnavailable diff --git a/backend/contracts/sendico/api/payments/immediate.yaml b/backend/contracts/sendico/api/payments/immediate.yaml deleted file mode 100644 index 2a7b885..0000000 --- a/backend/contracts/sendico/api/payments/immediate.yaml +++ /dev/null @@ -1,48 +0,0 @@ -post: - tags: [Payments] - summary: Initiate payment from immediate intent - description: | - Creates quote and executes a single payment in one call (request must include `intent`). For - raw `card` destinations, execution requires `exp_month` and `exp_year`; `cardToken` - destinations only require `token`. Card-recipient AML screening occurs during execution. - An unchanged payment reuses its persisted compliance decision on idempotent execution replay, - avoiding a duplicate AML call. A definitive denial terminates the payment with public - `failureCode: failure_restriction` and `failureReason: aml_denied`. - operationId: paymentsInitiateImmediate - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - requestBody: - required: true - content: - application/json: - schema: - allOf: - - $ref: ./request/payment.yaml#/components/schemas/InitiatePaymentRequest - - type: object - required: - - intent - not: - required: - - quoteRef - responses: - '200': - description: Payment initiated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/list.yaml b/backend/contracts/sendico/api/payments/list.yaml deleted file mode 100644 index e01a5c9..0000000 --- a/backend/contracts/sendico/api/payments/list.yaml +++ /dev/null @@ -1,76 +0,0 @@ -get: - tags: [Payments] - summary: List payments in organization - description: | - Returns organization payments with cursor pagination and optional filters. - State filters accept aliases (`accepted`, `settled`, `failed`, etc.) or canonical orchestration states. - operationId: paymentsList - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/cursor.yaml#/components/parameters/Cursor - - $ref: ../parameters/limit.yaml#/components/parameters/Limit - - name: quotation_ref - in: query - required: false - description: Filter by quotation reference used for execution. - schema: - type: string - - name: quote_ref - in: query - required: false - description: Alias of `quotation_ref`. - schema: - type: string - - name: created_from - in: query - required: false - description: Include payments created at or after this RFC3339 timestamp. - schema: - type: string - format: date-time - - name: createdFrom - in: query - required: false - description: Alias of `created_from`. - schema: - type: string - format: date-time - - name: created_to - in: query - required: false - description: Include payments created before this RFC3339 timestamp (must be after `created_from`). - schema: - type: string - format: date-time - - name: createdTo - in: query - required: false - description: Alias of `created_to`. - schema: - type: string - format: date-time - - $ref: ../parameters/state.yaml#/components/parameters/State - - $ref: ../parameters/states.yaml#/components/parameters/States - - $ref: ../parameters/filter_states.yaml#/components/parameters/FilterStates - responses: - '200': - description: Payments list - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentsData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/multiquote.yaml b/backend/contracts/sendico/api/payments/multiquote.yaml deleted file mode 100644 index 3ac6728..0000000 --- a/backend/contracts/sendico/api/payments/multiquote.yaml +++ /dev/null @@ -1,37 +0,0 @@ -post: - tags: [Payments] - summary: Quote multiple payment intents - description: | - Calculates a batch quote for multiple payment intents in one request. Each item includes - `executable`, `state`, and optional `blockReason` so callers can skip blocked items. Raw - `card` destinations can omit expiry while quoting, but quoted items intended for later - execution should include `exp_month` and `exp_year`. Card-recipient AML screening is not - performed during quotation; it runs during payment execution, so an executable quote does - not guarantee AML acceptance. - operationId: paymentsMultiQuote - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - requestBody: - $ref: ./bodies/payment.yaml#/components/requestBodies/QuotePaymentsBody - responses: - '200': - description: Quotes generated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentQuotesData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/object.yaml b/backend/contracts/sendico/api/payments/object.yaml deleted file mode 100644 index 0b71752..0000000 --- a/backend/contracts/sendico/api/payments/object.yaml +++ /dev/null @@ -1,31 +0,0 @@ -get: - tags: [Payments] - summary: Get payment by reference - description: Returns a single payment by `payments_ref`. - operationId: paymentsGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/payments_ref.yaml#/components/parameters/PaymentsRef - responses: - '200': - description: Payment data - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/quote.yaml b/backend/contracts/sendico/api/payments/quote.yaml deleted file mode 100644 index d409554..0000000 --- a/backend/contracts/sendico/api/payments/quote.yaml +++ /dev/null @@ -1,37 +0,0 @@ -post: - tags: [Payments] - summary: Quote single payment intent - description: | - Calculates quote for one payment intent without executing payment. The response includes - `quote.executable`, `quote.state`, and optional `quote.blockReason` so clients can reject - non-executable quotes before attempting execution. Raw `card` destinations can omit expiry - while quoting, but quotes intended for later execution should include `exp_month` and - `exp_year`. Card-recipient AML screening is not performed during quotation; it runs during - payment execution, so an executable quote does not guarantee AML acceptance. - operationId: paymentsQuote - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - requestBody: - $ref: ./bodies/payment.yaml#/components/requestBodies/QuotePaymentBody - responses: - '200': - description: Quote generated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/payment.yaml#/components/schemas/PaymentQuoteData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/registry.yaml b/backend/contracts/sendico/api/payments/registry.yaml deleted file mode 100644 index 42d796d..0000000 --- a/backend/contracts/sendico/api/payments/registry.yaml +++ /dev/null @@ -1,30 +0,0 @@ -get: - tags: [Payments] - summary: Get discovery registry snapshot for payments dependencies - description: Returns the latest discovery lookup payload used by payments integrations. - operationId: paymentsRegistryList - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - responses: - '200': - description: Discovery registry payload - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - type: object - additionalProperties: true - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/registry_refresh.yaml b/backend/contracts/sendico/api/payments/registry_refresh.yaml deleted file mode 100644 index 4719af7..0000000 --- a/backend/contracts/sendico/api/payments/registry_refresh.yaml +++ /dev/null @@ -1,32 +0,0 @@ -get: - tags: [Payments] - summary: Get latest discovery refresh event for payments dependencies - description: Returns the latest refresh event consumed by the payments API. - operationId: paymentsRegistryRefreshGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - responses: - '200': - description: Discovery refresh payload - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - type: - - object - - 'null' - additionalProperties: true - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/payments/request/payment.yaml b/backend/contracts/sendico/api/payments/request/payment.yaml deleted file mode 100644 index 3c425e3..0000000 --- a/backend/contracts/sendico/api/payments/request/payment.yaml +++ /dev/null @@ -1,163 +0,0 @@ -components: - schemas: - PaymentBase: - description: Base fields shared by payment initiation request payloads. - type: object - additionalProperties: false - required: - - idempotencyKey - properties: - idempotencyKey: - description: Client-supplied key used to safely deduplicate requests. - type: string - metadata: - description: Optional request metadata forwarded through payment processing. - type: object - additionalProperties: - type: string - - QuotePaymentRequest: - description: | - Request payload to quote a single payment intent. - For raw `card` destinations, quotation only requires `pan`. - If the quoted payment may later be executed, include `exp_month` and `exp_year` during quotation because execution reuses the quoted endpoint snapshot. - type: object - additionalProperties: false - required: - - intent - properties: - idempotencyKey: - description: Idempotency key used when persisting quote context. - type: string - metadata: - description: Optional metadata associated with the quote request. - type: object - additionalProperties: - type: string - clientPaymentRef: - description: Optional caller-side payment correlation reference to persist with the quote and reuse during execution when the execute request omits it. - type: string - intent: - description: Payment intent to be priced. See `PaymentIntent.destination` for raw-card quote versus execution requirements. - $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent - previewOnly: - description: If true, returns a preview quote without requiring idempotency. - type: boolean - allOf: - - if: - properties: - previewOnly: - const: true - required: - - previewOnly - then: - not: - required: - - idempotencyKey - else: - required: - - idempotencyKey - - QuotePaymentsRequest: - description: | - Request payload to quote multiple payment intents in a single call. - For raw `card` destinations, quotation only requires `pan`. - If quoted items may later be executed, include `exp_month` and `exp_year` during quotation because execution reuses the quoted endpoint snapshot. - type: object - additionalProperties: false - required: - - intents - properties: - idempotencyKey: - description: Idempotency key used when persisting batch quote context. - type: string - metadata: - description: Optional metadata associated with the quote request. - type: object - additionalProperties: - type: string - clientPaymentRef: - description: Optional caller-side batch payment correlation reference to persist with the quote and reuse during execution when the execute request omits it. - type: string - intents: - description: List of payment intents to be priced. See each intent destination for raw-card quote versus execution requirements. - type: array - minItems: 1 - items: - $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent - previewOnly: - description: If true, returns preview quotes without requiring idempotency. - type: boolean - allOf: - - if: - properties: - previewOnly: - const: true - required: - - previewOnly - then: - not: - required: - - idempotencyKey - else: - required: - - idempotencyKey - - InitiatePaymentRequest: - description: | - Request payload to initiate a single payment. - When executing a raw `card` destination from inline `intent`, `pan`, `exp_month`, and `exp_year` must be present. - When executing a `cardToken` destination, only `token` is required by current logic. - allOf: - - $ref: ./payment.yaml#/components/schemas/PaymentBase - - type: object - additionalProperties: false - oneOf: - - required: [intent] - - required: [quoteRef] - properties: - intent: - description: Payment intent to execute directly. Raw `card` destinations require expiry at execution time. - $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentIntent - quoteRef: - description: Reference to a previously generated quote to execute. If that quote contains a raw `card` destination, the original quote must already include `exp_month` and `exp_year`. - type: string - clientPaymentRef: - description: Optional caller-side payment correlation reference. When omitted, execution may reuse the value stored with the quote. - type: string - - InitiatePaymentsRequest: - description: | - Request payload to initiate multiple payments from a multi-quote reference. - If any quoted item contains a raw `card` destination, that item's original quote must already include `exp_month` and `exp_year`. - allOf: - - $ref: ./payment.yaml#/components/schemas/PaymentBase - - type: object - additionalProperties: false - required: - - quoteRef - properties: - quoteRef: - description: Reference to a previously generated multi-quote. Raw-card items execute from the stored quoted endpoint snapshot. - type: string - clientPaymentRef: - description: Optional caller-side payment correlation reference. When omitted, execution may reuse the value stored with the quote. - type: string - - InitiatePaymentByQuoteRequest: - description: | - Request payload to initiate one payment from an existing quote reference. - If the quote contains a raw `card` destination, the original quote must already include `exp_month` and `exp_year`. - allOf: - - $ref: ./payment.yaml#/components/schemas/PaymentBase - - type: object - additionalProperties: false - required: - - quoteRef - properties: - quoteRef: - description: Reference to a previously generated quote to execute. Execution reuses the stored quoted endpoint snapshot. - type: string - clientPaymentRef: - description: Optional caller-side payment correlation reference. When omitted, execution may reuse the value stored with the quote. - type: string diff --git a/backend/contracts/sendico/api/payments/response/payment.yaml b/backend/contracts/sendico/api/payments/response/payment.yaml deleted file mode 100644 index d4c2938..0000000 --- a/backend/contracts/sendico/api/payments/response/payment.yaml +++ /dev/null @@ -1,114 +0,0 @@ -components: - schemas: - PaymentQuoteData: - description: Response payload for a single payment quote. - type: object - additionalProperties: false - required: - - accessToken - - quote - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - idempotencyKey: - description: Idempotency key associated with the quote response. - type: string - clientPaymentRef: - description: Caller-supplied payment correlation reference echoed from the quote request, when provided. - type: string - quote: - description: Generated quote data for the requested payment intent. - $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentQuote - - PaymentQuotesData: - description: Response payload for a batch quote request. - type: object - additionalProperties: false - required: - - accessToken - - quote - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - quote: - description: Batch quote summary and quoted items. - type: object - additionalProperties: false - properties: - idempotencyKey: - description: Idempotency key associated with the batch quote response. - type: string - clientPaymentRef: - description: Caller-supplied payment correlation reference echoed from the batch quote request, when provided. - type: string - quoteRef: - description: Reference to the generated batch quote. - type: string - items: - description: Collection of quotes for each requested payment intent. - type: array - items: - $ref: ../../../models/payment/payment.yaml#/components/schemas/PaymentQuote - - PaymentsData: - description: Response payload containing a list of payments. - type: object - additionalProperties: false - required: - - accessToken - - payments - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - payments: - description: Collection of payment records. - type: array - items: - $ref: ../../../models/payment/payment.yaml#/components/schemas/Payment - page: - description: Pagination cursor metadata for payment listing endpoints. - $ref: ../../../models/common/pagination.yaml#/components/schemas/CursorPageResponse - - PaymentData: - description: Response payload containing a single payment record. - type: object - additionalProperties: false - required: - - accessToken - - payment - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - payment: - description: Requested payment record. - $ref: ../../../models/payment/payment.yaml#/components/schemas/Payment - - FXCurrenciesData: - description: Response payload containing the BFF startup-cached Treasury FX currency catalogue projection. - type: object - additionalProperties: false - required: - - currencies - properties: - currencies: - description: Client-visible currencies backed by Treasury Currency Catalog metadata. - type: array - items: - $ref: ../../../models/payment/fx_metadata.yaml#/components/schemas/FXCurrency - - FXDirectionsData: - description: Response payload containing organization-level directed FX availability. - type: object - additionalProperties: false - required: - - directions - properties: - directions: - description: Directions with at least one eligible direction-level route for the organization. - type: array - items: - $ref: ../../../models/payment/fx_metadata.yaml#/components/schemas/FXDirection diff --git a/backend/contracts/sendico/api/recipients/archive.yaml b/backend/contracts/sendico/api/recipients/archive.yaml deleted file mode 100644 index 4d3cb3f..0000000 --- a/backend/contracts/sendico/api/recipients/archive.yaml +++ /dev/null @@ -1,37 +0,0 @@ -get: - tags: [Recipients] - summary: Archive/unarchive recipient - description: Sets recipient archive state by `recipients_ref` and required `archived` query parameter. - operationId: recipientsArchive - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - - $ref: ../parameters/recipients_ref.yaml#/components/parameters/RecipientsRef - - name: archived - in: query - required: true - description: Target archive value to set on the recipient. - schema: - type: boolean - - $ref: ../parameters/cascade.yaml#/components/parameters/Cascade - responses: - '200': - description: Archive state updated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/recipient.yaml#/components/schemas/RecipientsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/recipients/bodies/recipient.yaml b/backend/contracts/sendico/api/recipients/bodies/recipient.yaml deleted file mode 100644 index 5785002..0000000 --- a/backend/contracts/sendico/api/recipients/bodies/recipient.yaml +++ /dev/null @@ -1,17 +0,0 @@ -components: - requestBodies: - RecipientCreateBody: - description: JSON body containing recipient details for create. - required: true - content: - application/json: - schema: - $ref: ../request/recipient.yaml#/components/schemas/RecipientCreateRequest - - RecipientUpdateBody: - description: JSON body containing recipient details for update. - required: true - content: - application/json: - schema: - $ref: ../request/recipient.yaml#/components/schemas/RecipientUpdateRequest diff --git a/backend/contracts/sendico/api/recipients/create.yaml b/backend/contracts/sendico/api/recipients/create.yaml deleted file mode 100644 index 10fc1ba..0000000 --- a/backend/contracts/sendico/api/recipients/create.yaml +++ /dev/null @@ -1,33 +0,0 @@ -post: - tags: [Recipients] - summary: Create recipient - description: Creates a recipient inside the organization identified by `org_ref`. - operationId: recipientsCreate - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - requestBody: - $ref: ./bodies/recipient.yaml#/components/requestBodies/RecipientCreateBody - responses: - '201': - description: Recipient created - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/recipient.yaml#/components/schemas/RecipientsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/recipients/list.yaml b/backend/contracts/sendico/api/recipients/list.yaml deleted file mode 100644 index 33ca97f..0000000 --- a/backend/contracts/sendico/api/recipients/list.yaml +++ /dev/null @@ -1,36 +0,0 @@ -get: - tags: [Recipients] - summary: List recipients - description: | - Returns recipients for the specified organization context. - Supports offset pagination and optional archive filtering. - The route currently includes both `org_ref` and `organizations_ref`; the handler resolves organization context from `org_ref` and ignores `organizations_ref`. - operationId: recipientsList - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/org_ref.yaml#/components/parameters/OrgRef - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - - $ref: ../parameters/limit.yaml#/components/parameters/Limit - - $ref: ../parameters/offset.yaml#/components/parameters/Offset - - $ref: ../parameters/archived.yaml#/components/parameters/Archived - responses: - '200': - description: Recipient list - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/recipient.yaml#/components/schemas/RecipientsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/recipients/object.yaml b/backend/contracts/sendico/api/recipients/object.yaml deleted file mode 100644 index 091e5c7..0000000 --- a/backend/contracts/sendico/api/recipients/object.yaml +++ /dev/null @@ -1,64 +0,0 @@ -get: - tags: [Recipients] - summary: Get recipient by reference - description: Returns a recipient by `recipients_ref`. - operationId: recipientsGet - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/recipients_ref.yaml#/components/parameters/RecipientsRef - responses: - '200': - description: Recipient data - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/recipient.yaml#/components/schemas/RecipientsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError - -delete: - tags: [Recipients] - summary: Delete recipient - description: Deletes recipient by reference. Use `cascade=true` to remove dependent objects when supported. - operationId: recipientsDelete - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/recipients_ref.yaml#/components/parameters/RecipientsRef - - $ref: ../parameters/cascade.yaml#/components/parameters/Cascade - responses: - '200': - description: Recipient deleted - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/recipient.yaml#/components/schemas/RecipientsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/recipients/request/recipient.yaml b/backend/contracts/sendico/api/recipients/request/recipient.yaml deleted file mode 100644 index 19656c4..0000000 --- a/backend/contracts/sendico/api/recipients/request/recipient.yaml +++ /dev/null @@ -1,53 +0,0 @@ -components: - schemas: - RecipientBaseRequest: - type: object - additionalProperties: false - properties: - id: - description: Optional recipient identifier. Required by the current update handler. - $ref: ../../../models/objectid.yaml#/components/schemas/ObjectId - name: - description: Optional human-readable recipient name. - type: string - description: - description: Optional free-form recipient description. - type: string - nullable: true - email: - description: Optional recipient email address. - type: string - format: email - avatarUrl: - description: Optional avatar URL. - type: string - nullable: true - status: - description: Optional recipient status. When omitted, current handler behavior falls back to the model zero value (`ready`). - type: string - enum: - - ready - - registered - - notRegistered - type: - description: Optional recipient type. When omitted, current handler behavior falls back to the model zero value (`internal`). - type: string - enum: - - internal - - external - - RecipientCreateRequest: - description: | - Request payload used to create a recipient. - The current create handler does not require any JSON fields beyond a valid object payload; organization context comes from the `org_ref` path parameter. - allOf: - - $ref: '#/components/schemas/RecipientBaseRequest' - - RecipientUpdateRequest: - description: | - Request payload used to update a recipient. - The current PUT handler targets the record by `id` and then replaces it with the submitted values; omitted fields may therefore be reset to zero values. - required: - - id - allOf: - - $ref: '#/components/schemas/RecipientBaseRequest' diff --git a/backend/contracts/sendico/api/recipients/response/recipient.yaml b/backend/contracts/sendico/api/recipients/response/recipient.yaml deleted file mode 100644 index 337b850..0000000 --- a/backend/contracts/sendico/api/recipients/response/recipient.yaml +++ /dev/null @@ -1,18 +0,0 @@ -components: - schemas: - RecipientsAuthData: - description: Authenticated response payload containing recipients. - type: object - additionalProperties: false - required: - - accessToken - - recipients - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - recipients: - description: Collection of recipients available to the authenticated account. - type: array - items: - $ref: ../../../models/recipient/recipient.yaml#/components/schemas/Recipient diff --git a/backend/contracts/sendico/api/recipients/update.yaml b/backend/contracts/sendico/api/recipients/update.yaml deleted file mode 100644 index f814f5b..0000000 --- a/backend/contracts/sendico/api/recipients/update.yaml +++ /dev/null @@ -1,31 +0,0 @@ -put: - tags: [Recipients] - summary: Update recipient - description: Updates recipient fields from request payload. The current handler requires `id`; omitted fields may be replaced with zero values. - operationId: recipientsUpdate - security: - - bearerAuth: [] - requestBody: - $ref: ./bodies/recipient.yaml#/components/requestBodies/RecipientUpdateBody - responses: - '200': - description: Recipient updated - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/recipient.yaml#/components/schemas/RecipientsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/response/error.yaml b/backend/contracts/sendico/api/response/error.yaml deleted file mode 100644 index c4a7014..0000000 --- a/backend/contracts/sendico/api/response/error.yaml +++ /dev/null @@ -1,24 +0,0 @@ -components: - schemas: - ApiError: - description: Standard API error envelope with optional debugging details. - type: object - additionalProperties: false - required: - - code - - error - - source - properties: - code: - description: Application-specific numeric error code. - type: integer - format: int32 - error: - description: Human-readable error summary. - type: string - source: - description: Component or subsystem that produced the error. - type: string - details: - description: Optional detailed message providing additional failure context. - type: string diff --git a/backend/contracts/sendico/api/response/operation.yaml b/backend/contracts/sendico/api/response/operation.yaml deleted file mode 100644 index 08c5a5f..0000000 --- a/backend/contracts/sendico/api/response/operation.yaml +++ /dev/null @@ -1,85 +0,0 @@ -components: - responses: - Ok: - description: Successful response - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/BaseResponse - - Created: - description: Resource created - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/BaseResponse - - Accepted: - description: Request accepted for processing - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/BaseResponse - - BadRequest: - description: Bad request payload or query/path parameter - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - Unauthorized: - description: Unauthorized - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - Forbidden: - description: Forbidden - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - NotFound: - description: Resource not found - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - Gone: - description: Resource is no longer available - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - Conflict: - description: Conflict - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - TooManyRequests: - description: Too many requests - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - ServiceUnavailable: - description: Service unavailable - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse - - InternalServerError: - description: Internal server error - content: - application/json: - schema: - $ref: ./response.yaml#/components/schemas/ErrorResponse diff --git a/backend/contracts/sendico/api/response/response.yaml b/backend/contracts/sendico/api/response/response.yaml deleted file mode 100644 index 89589e5..0000000 --- a/backend/contracts/sendico/api/response/response.yaml +++ /dev/null @@ -1,47 +0,0 @@ -components: - schemas: - BaseResponse: - type: object - additionalProperties: false - required: - - status - - data - properties: - status: - $ref: ./status.yaml#/components/schemas/ApiStatus - data: - description: Payload for success or error responses. - - ErrorResponse: - allOf: - - $ref: ./response.yaml#/components/schemas/BaseResponse - - type: object - properties: - status: - type: string - enum: - - error - data: - $ref: ./error.yaml#/components/schemas/ApiError - - SuccessResultData: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - - SuccessResultResponse: - allOf: - - $ref: ./response.yaml#/components/schemas/BaseResponse - - type: object - properties: - status: - type: string - enum: - - success - - processed - data: - $ref: ./response.yaml#/components/schemas/SuccessResultData diff --git a/backend/contracts/sendico/api/response/status.yaml b/backend/contracts/sendico/api/response/status.yaml deleted file mode 100644 index 1a7e123..0000000 --- a/backend/contracts/sendico/api/response/status.yaml +++ /dev/null @@ -1,10 +0,0 @@ -components: - schemas: - ApiStatus: - type: string - description: Unified response status used by backend wrappers. - enum: - - success - - processed - - error - - request diff --git a/backend/contracts/sendico/api/verification/bodies/verification.yaml b/backend/contracts/sendico/api/verification/bodies/verification.yaml deleted file mode 100644 index da9dbd6..0000000 --- a/backend/contracts/sendico/api/verification/bodies/verification.yaml +++ /dev/null @@ -1,17 +0,0 @@ -components: - requestBodies: - VerificationCodeBody: - description: JSON payload used to request a new verification code. - required: true - content: - application/json: - schema: - $ref: ../request/verification.yaml#/components/schemas/VerificationCodeRequest - - VerifyCodeBody: - description: JSON payload containing a verification code to validate. - required: true - content: - application/json: - schema: - $ref: ../request/verification.yaml#/components/schemas/CodeVerificationRequest diff --git a/backend/contracts/sendico/api/verification/create.yaml b/backend/contracts/sendico/api/verification/create.yaml deleted file mode 100644 index 9dd4e97..0000000 --- a/backend/contracts/sendico/api/verification/create.yaml +++ /dev/null @@ -1,37 +0,0 @@ -post: - tags: [Verification, Auth] - summary: Request verification code - description: | - Creates a verification challenge for the specified purpose/target. - For `purpose=login`, request must be authorized with a pending account token. - operationId: verificationRequestCode - security: - - bearerAuth: [] - requestBody: - $ref: ./bodies/verification.yaml#/components/requestBodies/VerificationCodeBody - responses: - '202': - description: Verification code requested - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/verification.yaml#/components/schemas/VerificationResponseData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '429': - $ref: ../response/operation.yaml#/components/responses/TooManyRequests - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/verification/request/verification.yaml b/backend/contracts/sendico/api/verification/request/verification.yaml deleted file mode 100644 index a005098..0000000 --- a/backend/contracts/sendico/api/verification/request/verification.yaml +++ /dev/null @@ -1,73 +0,0 @@ -components: - schemas: - VerificationCodeRequest: - type: object - additionalProperties: false - required: - - purpose - properties: - purpose: - type: string - description: Verification purpose, e.g. login. - target: - type: string - format: email - description: Optional destination override. When omitted, current handler falls back to the authenticated account email/login. - idempotencyKey: - type: string - quoteRef: - description: Required when `purpose` is `payout`; omitted for login and other verification purposes. - type: string - allOf: - - if: - properties: - purpose: - const: payout - required: - - purpose - then: - required: - - quoteRef - - CodeVerificationRequest: - type: object - additionalProperties: false - required: - - purpose - - code - properties: - purpose: - type: string - description: Verification purpose, e.g. login. - target: - type: string - format: email - description: Optional destination override. When omitted, current handler falls back to the authenticated account email/login. - idempotencyKey: - type: string - quoteRef: - description: Required when `purpose` is `payout`; omitted for login and other verification purposes. - type: string - code: - type: string - sessionIdentifier: - $ref: ../../../models/auth/session_identifier.yaml#/components/schemas/SessionIdentifier - allOf: - - if: - properties: - purpose: - const: login - required: - - purpose - then: - required: - - sessionIdentifier - - if: - properties: - purpose: - const: payout - required: - - purpose - then: - required: - - quoteRef diff --git a/backend/contracts/sendico/api/verification/resend.yaml b/backend/contracts/sendico/api/verification/resend.yaml deleted file mode 100644 index d2ee0fc..0000000 --- a/backend/contracts/sendico/api/verification/resend.yaml +++ /dev/null @@ -1,37 +0,0 @@ -post: - tags: [Verification, Auth] - summary: Resend verification code - description: | - Re-issues a verification challenge using the same request shape as `/verification`. - Cooldown and retry limits are enforced by backend verification storage policy. - operationId: verificationResendCode - security: - - bearerAuth: [] - requestBody: - $ref: ./bodies/verification.yaml#/components/requestBodies/VerificationCodeBody - responses: - '202': - description: Verification code resent - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/verification.yaml#/components/schemas/VerificationResponseData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '429': - $ref: ../response/operation.yaml#/components/responses/TooManyRequests - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/verification/response/verification.yaml b/backend/contracts/sendico/api/verification/response/verification.yaml deleted file mode 100644 index f169ab7..0000000 --- a/backend/contracts/sendico/api/verification/response/verification.yaml +++ /dev/null @@ -1,35 +0,0 @@ -components: - schemas: - VerificationResponseData: - description: Response data returned after requesting a verification code. - type: object - additionalProperties: false - required: - - idempotencyKey - - ttl_seconds - - cooldown_seconds - - target - properties: - idempotencyKey: - description: Idempotency key associated with the verification request. - type: string - ttl_seconds: - description: Verification code validity period in seconds. - type: integer - format: int32 - cooldown_seconds: - description: Cooldown period before another code can be requested. - type: integer - format: int32 - target: - description: Destination where the verification code was sent. - type: string - quoteRef: - description: Payment quote reference echoed for payout verification requests. - type: string - - VerifyResultData: - description: Result payload returned after code verification succeeds. - oneOf: - - $ref: ../../accounts/response/auth.yaml#/components/schemas/LoginData - - $ref: ../../response/response.yaml#/components/schemas/SuccessResultData diff --git a/backend/contracts/sendico/api/verification/verify.yaml b/backend/contracts/sendico/api/verification/verify.yaml deleted file mode 100644 index cf26d7b..0000000 --- a/backend/contracts/sendico/api/verification/verify.yaml +++ /dev/null @@ -1,37 +0,0 @@ -post: - tags: [Verification, Auth] - summary: Verify code and complete pending flow - description: | - Verifies submitted code and consumes verification token. - For login purpose, returns full auth payload (access + refresh tokens); for other purposes returns operation result. - operationId: verificationVerifyCode - security: - - bearerAuth: [] - requestBody: - $ref: ./bodies/verification.yaml#/components/requestBodies/VerifyCodeBody - responses: - '200': - description: Verification successful - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/verification.yaml#/components/schemas/VerifyResultData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '404': - $ref: ../response/operation.yaml#/components/responses/NotFound - '410': - $ref: ../response/operation.yaml#/components/responses/Gone - '409': - $ref: ../response/operation.yaml#/components/responses/Conflict - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/wallets/assets.yaml b/backend/contracts/sendico/api/wallets/assets.yaml deleted file mode 100644 index 17c8b22..0000000 --- a/backend/contracts/sendico/api/wallets/assets.yaml +++ /dev/null @@ -1,35 +0,0 @@ -get: - tags: [Wallets] - summary: List live managed-wallet assets - description: | - Returns the live wallet asset catalogue currently announced by healthy CRYPTO - gateways in discovery. The endpoint is authenticated and authorized by - `organizations_ref`, but the returned asset list is gateway-driven rather than - organization-customized. Malformed gateway asset announcements are skipped. - If discovery is available but no valid wallet assets are currently announced, - the response contains an empty `assets` array. - operationId: walletAssetsList - security: - - bearerAuth: [] - parameters: - - $ref: ../parameters/organizations_ref.yaml#/components/parameters/OrganizationsRef - responses: - '200': - description: Live managed-wallet asset catalogue - content: - application/json: - schema: - allOf: - - $ref: ../response/response.yaml#/components/schemas/BaseResponse - - type: object - properties: - data: - $ref: ./response/wallet.yaml#/components/schemas/WalletAssetsAuthData - '400': - $ref: ../response/operation.yaml#/components/responses/BadRequest - '401': - $ref: ../response/operation.yaml#/components/responses/Unauthorized - '403': - $ref: ../response/operation.yaml#/components/responses/Forbidden - '500': - $ref: ../response/operation.yaml#/components/responses/InternalServerError diff --git a/backend/contracts/sendico/api/wallets/response/wallet.yaml b/backend/contracts/sendico/api/wallets/response/wallet.yaml deleted file mode 100644 index b390b85..0000000 --- a/backend/contracts/sendico/api/wallets/response/wallet.yaml +++ /dev/null @@ -1,18 +0,0 @@ -components: - schemas: - WalletAssetsAuthData: - description: Authenticated response payload containing live wallet assets. - type: object - additionalProperties: false - required: - - accessToken - - assets - properties: - accessToken: - description: Refreshed access token to be used in subsequent API calls. - $ref: ../../../models/auth/token_data.yaml#/components/schemas/TokenData - assets: - description: Managed-wallet assets currently announced by healthy CRYPTO gateways. - type: array - items: - $ref: ../../../models/wallet/wallet_asset.yaml#/components/schemas/WalletAsset diff --git a/backend/contracts/sendico/external/chain_network.yaml b/backend/contracts/sendico/external/chain_network.yaml deleted file mode 100644 index c577011..0000000 --- a/backend/contracts/sendico/external/chain_network.yaml +++ /dev/null @@ -1,10 +0,0 @@ -components: - schemas: - ChainNetwork: - type: string - enum: - - unspecified - - ethereum_mainnet - - arbitrum_one - - tron_mainnet - - tron_nile diff --git a/backend/contracts/sendico/external/endpoint_type.yaml b/backend/contracts/sendico/external/endpoint_type.yaml deleted file mode 100644 index 3a23e42..0000000 --- a/backend/contracts/sendico/external/endpoint_type.yaml +++ /dev/null @@ -1,15 +0,0 @@ -components: - schemas: - EndpointType: - type: string - enum: - - paymentMethod - - account - - recipient - - ledger - - managedWallet - - cryptoAddress - - card - - cardToken - - bankAccount - - iban diff --git a/backend/contracts/sendico/external/fx_side.yaml b/backend/contracts/sendico/external/fx_side.yaml deleted file mode 100644 index 639e6f2..0000000 --- a/backend/contracts/sendico/external/fx_side.yaml +++ /dev/null @@ -1,7 +0,0 @@ -components: - schemas: - FxSide: - type: string - enum: - - buy_base_sell_quote - - sell_base_buy_quote diff --git a/backend/contracts/sendico/external/payment_kind.yaml b/backend/contracts/sendico/external/payment_kind.yaml deleted file mode 100644 index 06f68ef..0000000 --- a/backend/contracts/sendico/external/payment_kind.yaml +++ /dev/null @@ -1,8 +0,0 @@ -components: - schemas: - PaymentKind: - type: string - enum: - - payout - - internal_transfer - - fx_conversion diff --git a/backend/contracts/sendico/external/payment_method_type.yaml b/backend/contracts/sendico/external/payment_method_type.yaml deleted file mode 100644 index c8c4e84..0000000 --- a/backend/contracts/sendico/external/payment_method_type.yaml +++ /dev/null @@ -1,12 +0,0 @@ -components: - schemas: - PaymentMethodType: - type: string - enum: - - iban - - card - - cardToken - - bankAccount - - wallet - - cryptoAddress - - ledger diff --git a/backend/contracts/sendico/external/payment_state.yaml b/backend/contracts/sendico/external/payment_state.yaml deleted file mode 100644 index 9685cd8..0000000 --- a/backend/contracts/sendico/external/payment_state.yaml +++ /dev/null @@ -1,11 +0,0 @@ -components: - schemas: - PaymentState: - type: string - enum: - - orchestration_state_unspecified - - orchestration_state_created - - orchestration_state_executing - - orchestration_state_needs_attention - - orchestration_state_settled - - orchestration_state_failed diff --git a/backend/contracts/sendico/external/settlement_mode.yaml b/backend/contracts/sendico/external/settlement_mode.yaml deleted file mode 100644 index e5531c9..0000000 --- a/backend/contracts/sendico/external/settlement_mode.yaml +++ /dev/null @@ -1,8 +0,0 @@ -components: - schemas: - SettlementMode: - type: string - enum: - - unspecified - - fix_source - - fix_received diff --git a/backend/contracts/sendico/models/account/account.yaml b/backend/contracts/sendico/models/account/account.yaml deleted file mode 100644 index b47ae25..0000000 --- a/backend/contracts/sendico/models/account/account.yaml +++ /dev/null @@ -1,15 +0,0 @@ -components: - schemas: - AccountData: - description: Extended account payload including visibility-specific metadata. - allOf: - - $ref: ./account_public.yaml#/components/schemas/AccountPublic - - type: object - description: Account attributes that are not part of the public profile. - additionalProperties: false - required: - - isAnonymous - properties: - isAnonymous: - description: Indicates whether the account is marked as anonymous. - type: boolean diff --git a/backend/contracts/sendico/models/account/account_public.yaml b/backend/contracts/sendico/models/account/account_public.yaml deleted file mode 100644 index 3f80789..0000000 --- a/backend/contracts/sendico/models/account/account_public.yaml +++ /dev/null @@ -1,33 +0,0 @@ -components: - schemas: - AccountPublic: - description: Public account profile data exposed in API responses. - allOf: - - $ref: ../storable.yaml#/components/schemas/Storable - - $ref: ../common/describable.yaml#/components/schemas/Describable - - type: object - description: Account-specific public attributes. - additionalProperties: false - required: - - login - - locale - - lastName - - isArchived - properties: - login: - description: Account login identifier represented as an email address. - type: string - format: email - locale: - description: Preferred locale used for account-facing content. - type: string - lastName: - description: Account holder last name. - type: string - avatarUrl: - description: Optional URL of the account avatar image. - type: string - nullable: true - isArchived: - description: Indicates whether the account is archived and inactive. - type: boolean diff --git a/backend/contracts/sendico/models/auth/client_refresh_token.yaml b/backend/contracts/sendico/models/auth/client_refresh_token.yaml deleted file mode 100644 index 43408e3..0000000 --- a/backend/contracts/sendico/models/auth/client_refresh_token.yaml +++ /dev/null @@ -1,15 +0,0 @@ -components: - schemas: - ClientRefreshToken: - description: Refresh token bound to a specific client and device session. - allOf: - - $ref: ./session_identifier.yaml#/components/schemas/SessionIdentifier - - type: object - description: Refresh-token payload associated with the session identifier. - additionalProperties: false - required: - - token - properties: - token: - description: Long-lived token used to obtain a new access token. - type: string diff --git a/backend/contracts/sendico/models/auth/login_data.yaml b/backend/contracts/sendico/models/auth/login_data.yaml deleted file mode 100644 index 480b3d2..0000000 --- a/backend/contracts/sendico/models/auth/login_data.yaml +++ /dev/null @@ -1,21 +0,0 @@ -components: - schemas: - LoginData: - description: Credentials and optional locale used during user sign-in. - type: object - additionalProperties: false - required: - - login - - password - properties: - login: - description: User login identifier, represented as an email address. - type: string - format: email - password: - description: User password submitted for authentication. - type: string - format: password - locale: - description: Optional locale preference provided at login time. - type: string diff --git a/backend/contracts/sendico/models/auth/session_identifier.yaml b/backend/contracts/sendico/models/auth/session_identifier.yaml deleted file mode 100644 index fba55b0..0000000 --- a/backend/contracts/sendico/models/auth/session_identifier.yaml +++ /dev/null @@ -1,16 +0,0 @@ -components: - schemas: - SessionIdentifier: - description: Identifies an authentication session by client and device. - type: object - additionalProperties: false - required: - - clientId - - deviceId - properties: - clientId: - description: Client application identifier associated with the session. - type: string - deviceId: - description: Device/server identifier associated with the client session. - type: string diff --git a/backend/contracts/sendico/models/auth/token_data.yaml b/backend/contracts/sendico/models/auth/token_data.yaml deleted file mode 100644 index 5e6264d..0000000 --- a/backend/contracts/sendico/models/auth/token_data.yaml +++ /dev/null @@ -1,17 +0,0 @@ -components: - schemas: - TokenData: - description: Authentication token payload with expiration metadata. - type: object - additionalProperties: false - required: - - token - - expiration - properties: - token: - description: Issued authentication token value. - type: string - expiration: - description: RFC 3339 timestamp when the token expires. - type: string - format: date-time diff --git a/backend/contracts/sendico/models/callback/callback.yaml b/backend/contracts/sendico/models/callback/callback.yaml deleted file mode 100644 index f555a05..0000000 --- a/backend/contracts/sendico/models/callback/callback.yaml +++ /dev/null @@ -1,152 +0,0 @@ -components: - schemas: - CallbackBackoff: - description: Backoff window used between callback retry attempts. - type: object - additionalProperties: false - required: - - minDelayMs - - maxDelayMs - properties: - minDelayMs: - description: Minimum delay in milliseconds before scheduling a retry attempt. - type: integer - minimum: 1 - maxDelayMs: - description: Maximum delay in milliseconds before scheduling a retry attempt. - type: integer - minimum: 1 - - CallbackRetryPolicy: - description: Retry and delivery behavior for callback requests. - type: object - additionalProperties: false - required: - - backoff - - signingMode - - maxAttempts - - requestTimeoutMs - properties: - backoff: - description: Delay boundaries used to compute retry backoff between attempts. - $ref: '#/components/schemas/CallbackBackoff' - signingMode: - description: Request-signing strategy applied when dispatching callback payloads. `jws_eddsa_ed25519` sends the raw JSON body unchanged and includes a detached compact JWS in `X-Callback-JWS`. - type: string - enum: - - none - - hmac_sha256 - - jws_eddsa_ed25519 - headers: - description: Additional HTTP headers included with every callback request. - type: object - additionalProperties: - type: string - maxAttempts: - description: Maximum number of delivery attempts before marking dispatch as failed. - type: integer - minimum: 1 - requestTimeoutMs: - description: Per-request timeout in milliseconds for callback HTTP calls. - type: integer - minimum: 1 - - CallbackSigningJWK: - description: Ed25519 verification key published for callbacks that use detached JWS signing. - type: object - additionalProperties: false - required: - - kty - - crv - - alg - - use - - kid - - x - properties: - kty: - type: string - enum: [OKP] - crv: - type: string - enum: [Ed25519] - alg: - type: string - enum: [EdDSA] - use: - type: string - enum: [sig] - kid: - description: Opaque key identifier mirrored into the protected JWS header and `X-Callback-Key-Id`. - type: string - x: - description: Base64url-encoded Ed25519 public key bytes used to verify the detached JWS signature. - type: string - - CallbackSigningKey: - description: Verification material exposed for callbacks that use `jws_eddsa_ed25519`. Receivers verify `X-Callback-JWS` against the raw request body with the matching `kid`. Retiring keys remain available for 24 hours after rotation so queued deliveries remain verifiable. - type: object - additionalProperties: false - required: - - status - - jwk - properties: - status: - description: Indicates whether the key is the current signing key or a still-valid retiring key from a recent rotation. - type: string - enum: - - active - - retiring - expiresAt: - description: Timestamp after which a retiring key is no longer returned in callback reads. - type: string - format: date-time - nullable: true - jwk: - $ref: '#/components/schemas/CallbackSigningJWK' - - CallbackContent: - description: Callback endpoint configuration and delivery parameters. - allOf: - - type: object - additionalProperties: false - required: - - status - - url - - eventTypes - - retryPolicy - properties: - status: - description: Operational state controlling whether callback delivery is enabled. - type: string - enum: - - active - - disabled - url: - description: Absolute HTTPS endpoint URL that receives callback events. - type: string - format: uri - eventTypes: - description: Event type names that trigger callback delivery to this endpoint. - type: array - items: - type: string - retryPolicy: - description: Retry and timeout parameters used when dispatching callback events. - $ref: '#/components/schemas/CallbackRetryPolicy' - - - Callback: - description: Stored callback configuration bound to an organization permission scope. - allOf: - - $ref: ../permission_bound.yaml#/components/schemas/PermissionBound - - $ref: ../common/describable.yaml#/components/schemas/Describable - - $ref: '#/components/schemas/CallbackContent' - - type: object - additionalProperties: false - properties: - signingKeys: - description: Read-only verification keys for callbacks that use detached JWS signing. The callback JSON body is not wrapped; receivers verify `X-Callback-JWS` against the raw body bytes and use the key matching the protected-header `kid`. - type: array - readOnly: true - items: - $ref: '#/components/schemas/CallbackSigningKey' diff --git a/backend/contracts/sendico/models/common/describable.yaml b/backend/contracts/sendico/models/common/describable.yaml deleted file mode 100644 index 1bf6d5b..0000000 --- a/backend/contracts/sendico/models/common/describable.yaml +++ /dev/null @@ -1,16 +0,0 @@ -components: - schemas: - Describable: - description: Common naming metadata for user-defined entities. - type: object - additionalProperties: false - required: - - name - properties: - name: - description: Human-readable name of the entity. - type: string - description: - description: Optional free-form description of the entity. - type: string - nullable: true diff --git a/backend/contracts/sendico/models/common/money.yaml b/backend/contracts/sendico/models/common/money.yaml deleted file mode 100644 index b130b4c..0000000 --- a/backend/contracts/sendico/models/common/money.yaml +++ /dev/null @@ -1,18 +0,0 @@ -components: - schemas: - Money: - description: Monetary value represented by a decimal amount and ISO currency code. - type: object - additionalProperties: false - required: - - amount - - currency - properties: - amount: - type: string - description: Decimal string amount. - example: '125.50' - currency: - description: ISO 4217 currency code for the provided amount. - type: string - example: USD diff --git a/backend/contracts/sendico/models/common/pagination.yaml b/backend/contracts/sendico/models/common/pagination.yaml deleted file mode 100644 index 2a923c3..0000000 --- a/backend/contracts/sendico/models/common/pagination.yaml +++ /dev/null @@ -1,10 +0,0 @@ -components: - schemas: - CursorPageResponse: - description: Cursor-based pagination metadata returned with list responses. - type: object - additionalProperties: false - properties: - next_cursor: - description: Opaque cursor to request the next page; omitted when no more items exist. - type: string diff --git a/backend/contracts/sendico/models/ledger/balance.yaml b/backend/contracts/sendico/models/ledger/balance.yaml deleted file mode 100644 index df4423b..0000000 --- a/backend/contracts/sendico/models/ledger/balance.yaml +++ /dev/null @@ -1,53 +0,0 @@ -components: - schemas: - LedgerAccountBalance: - description: Canonical ledger account balance. Current and historical modes have distinct field-presence semantics. - oneOf: - - $ref: ./balance.yaml#/components/schemas/CurrentLedgerAccountBalance - - $ref: ./balance.yaml#/components/schemas/HistoricalLedgerAccountBalance - - CurrentLedgerAccountBalance: - description: Current materialized ledger account balance. - type: object - additionalProperties: false - required: - - ledgerAccountRef - - balance - - version - - lastUpdated - properties: - ledgerAccountRef: - description: Ledger account reference. - $ref: ../objectid.yaml#/components/schemas/ObjectId - balance: - $ref: ../common/money.yaml#/components/schemas/Money - version: - description: Materialized account-balance version. - type: integer - format: int64 - lastUpdated: - description: Materialized balance update timestamp. - type: string - format: date-time - - HistoricalLedgerAccountBalance: - description: | - Event-time accounting balance reconstructed from posted statement-entry deltas - where `eventTime <= asOf`. This is not a materialized historical snapshot. - type: object - additionalProperties: false - required: - - ledgerAccountRef - - balance - - asOf - properties: - ledgerAccountRef: - description: Ledger account reference. - $ref: ../objectid.yaml#/components/schemas/ObjectId - balance: - $ref: ../common/money.yaml#/components/schemas/Money - asOf: - description: Canonical UTC RFC3339 event-time cutoff used for reconstruction. - type: string - format: date-time - example: '2026-04-23T12:30:00Z' diff --git a/backend/contracts/sendico/models/ledger/operation.yaml b/backend/contracts/sendico/models/ledger/operation.yaml deleted file mode 100644 index 60cb2bb..0000000 --- a/backend/contracts/sendico/models/ledger/operation.yaml +++ /dev/null @@ -1,124 +0,0 @@ -components: - schemas: - LedgerStatementVisibility: - description: Visibility of the account-history projection returned for the requested ledger account. - type: string - enum: - - full - - partial - - LedgerEntryType: - description: Lowercase ledger entry type string from the BFF response. - type: string - example: transfer - - LedgerLineType: - description: Lowercase posting-line type string from the BFF response. - type: string - example: main - - LedgerAccountOperationSummary: - description: | - Account-history summary projection for a ledger statement entry. - `accountAmount` is the exact decimal sum of visible posting-line amounts whose `ledgerAccountRef` - equals the requested statement account. Positive means net credit, negative means net debit, - zero means net neutral, and the currency matches the requested ledger account currency. - type: object - additionalProperties: false - required: - - entryRef - - entryType - - accountAmount - - eventTime - - version - - visibility - properties: - entryRef: - description: Statement entry reference. - $ref: ../objectid.yaml#/components/schemas/ObjectId - entryType: - $ref: ./operation.yaml#/components/schemas/LedgerEntryType - accountAmount: - $ref: ../common/money.yaml#/components/schemas/Money - description: - description: Human-readable entry description when present. - type: string - eventTime: - description: Event timestamp carried by the canonical statement entry. - type: string - format: date-time - version: - description: Immutable ledger history version used for deterministic ordering. - type: integer - format: int64 - visibility: - $ref: ./operation.yaml#/components/schemas/LedgerStatementVisibility - - LedgerAccountOperationLine: - description: Visible posting line included in the account-history projection. - type: object - additionalProperties: false - required: - - ledgerAccountRef - - money - - lineType - properties: - ledgerAccountRef: - description: Ledger account reference for the visible posting line. - $ref: ../objectid.yaml#/components/schemas/ObjectId - money: - $ref: ../common/money.yaml#/components/schemas/Money - lineType: - $ref: ./operation.yaml#/components/schemas/LedgerLineType - organizationRef: - description: Owning organization reference when visible to the caller. - $ref: ../objectid.yaml#/components/schemas/ObjectId - - LedgerAccountOperation: - description: | - Detailed account-history projection for a statement entry. - This payload is scoped to the requested ledger account and does not expose general journal-entry data outside that visibility model. - type: object - additionalProperties: false - required: - - entryRef - - entryType - - accountAmount - - eventTime - - version - - visibility - - lines - properties: - entryRef: - description: Statement entry reference. - $ref: ../objectid.yaml#/components/schemas/ObjectId - entryType: - $ref: ./operation.yaml#/components/schemas/LedgerEntryType - accountAmount: - $ref: ../common/money.yaml#/components/schemas/Money - description: - description: Human-readable entry description when present. - type: string - eventTime: - description: Event timestamp carried by the canonical statement entry. - type: string - format: date-time - version: - description: Immutable ledger history version used for deterministic ordering. - type: integer - format: int64 - visibility: - $ref: ./operation.yaml#/components/schemas/LedgerStatementVisibility - lines: - description: Visible posting lines included in the account-history projection. - type: array - items: - $ref: ./operation.yaml#/components/schemas/LedgerAccountOperationLine - metadata: - description: Entry metadata when visible to the caller. - type: object - additionalProperties: - type: string - organizationRef: - description: Entry organization reference when visible to the caller. - $ref: ../objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/models/objectid.yaml b/backend/contracts/sendico/models/objectid.yaml deleted file mode 100644 index 01f1c3d..0000000 --- a/backend/contracts/sendico/models/objectid.yaml +++ /dev/null @@ -1,9 +0,0 @@ -components: - schemas: - ObjectId: - description: Object identifier represented as a 24-character hexadecimal string. - type: string - format: objectid - pattern: '^[a-fA-F0-9]{24}$' - examples: - - 64f85f5f4c7dbf7cfb8f3f10 diff --git a/backend/contracts/sendico/models/organization/organization.yaml b/backend/contracts/sendico/models/organization/organization.yaml deleted file mode 100644 index 8b6dec1..0000000 --- a/backend/contracts/sendico/models/organization/organization.yaml +++ /dev/null @@ -1,39 +0,0 @@ -components: - schemas: - OrganizationBase: - allOf: - - $ref: ../permission_bound.yaml#/components/schemas/PermissionBound - - $ref: ../common/describable.yaml#/components/schemas/Describable - - type: object - additionalProperties: false - required: - - timeZone - properties: - tenantRef: - description: Tenant identifier that owns the organization. - $ref: ../objectid.yaml#/components/schemas/ObjectId - timeZone: - type: string - logoUrl: - type: string - nullable: true - signupState: - description: Signup/provisioning lifecycle state for the organization. - type: string - enum: - - pending_signup_provisioning - - blocked_signup_provisioning - - active - - Organization: - allOf: - - $ref: ./organization.yaml#/components/schemas/OrganizationBase - - type: object - additionalProperties: false - required: - - members - properties: - members: - type: array - items: - $ref: ../objectid.yaml#/components/schemas/ObjectId diff --git a/backend/contracts/sendico/models/payment/fx_metadata.yaml b/backend/contracts/sendico/models/payment/fx_metadata.yaml deleted file mode 100644 index 857a2a1..0000000 --- a/backend/contracts/sendico/models/payment/fx_metadata.yaml +++ /dev/null @@ -1,78 +0,0 @@ -components: - schemas: - FXCurrency: - description: Display-facing currency metadata sourced from the Treasury Currency Catalog startup snapshot cached by the BFF. - type: object - additionalProperties: false - required: - - code - - kind - properties: - code: - description: Treasury canonical asset code. - type: string - kind: - description: Compatibility asset kind projection. Use `assetKind` for the Treasury-owned exact asset kind. - type: string - enum: - - fiat - - crypto - - internal - assetKind: - description: Treasury-owned asset kind. Network-specific support, provider asset codes, token contracts, and provider rails are not represented here. - type: string - enum: - - fiat - - crypto_token - - crypto_native - - internal_platform - decimals: - description: Display-facing compatibility field backed by Treasury `displayScale`. Backend services and clients must not use it as accounting, quote, or payout execution precision authority. - type: integer - format: int32 - minimum: 0 - scale: - description: Display-facing compatibility field backed by Treasury `displayScale`; always equal to `decimals` in this response. - type: integer - format: int32 - minimum: 0 - displayScale: - description: Treasury UI/display precision. - type: integer - format: int32 - minimum: 0 - accountingScaleCandidate: - description: Informational Treasury accounting scale candidate. This is not provider execution precision, not FX rate precision, and not guaranteed executable payout precision or provider support. - type: integer - format: int32 - minimum: 0 - rounding: - description: Preferred rounding mode for the currency, when supplied by the oracle. - type: string - enum: - - round_half_even - - round_half_up - - round_down - - FXDirection: - description: Direction-level FX availability for an organization. - type: object - additionalProperties: false - required: - - fromCurrency - - toCurrency - - pair - - side - properties: - fromCurrency: - description: Source currency of the directed conversion. - type: string - toCurrency: - description: Target currency of the directed conversion. - type: string - pair: - description: Oracle canonical base/quote pair from which the direction was derived. This pair does not follow display from/to order. - $ref: ./payment.yaml#/components/schemas/CurrencyPair - side: - description: FX side interpreted against the canonical pair. `sell_base_buy_quote` means the customer sells base and receives quote; `buy_base_sell_quote` means the customer buys base and pays quote. - $ref: ../../external/fx_side.yaml#/components/schemas/FxSide diff --git a/backend/contracts/sendico/models/payment/payment.yaml b/backend/contracts/sendico/models/payment/payment.yaml deleted file mode 100644 index e970feb..0000000 --- a/backend/contracts/sendico/models/payment/payment.yaml +++ /dev/null @@ -1,879 +0,0 @@ -components: - schemas: - Asset: - description: Blockchain asset identifier used for on-chain transfers. - type: object - additionalProperties: false - properties: - chain: - description: Chain/network where the asset exists. - $ref: ../../external/chain_network.yaml#/components/schemas/ChainNetwork - token_symbol: - description: Symbol of the token on the selected chain. - type: string - contract_address: - description: Smart-contract address for tokenized assets. - type: string - - ExternalChainAsset: - description: Blockchain asset identifier used for external-chain endpoints. `token_symbol` is required by current quote/initiate logic; `chain` remains optional. - required: - - token_symbol - allOf: - - $ref: ./payment.yaml#/components/schemas/Asset - - LedgerEndpoint: - description: | - Internal ledger-based endpoint used as payment source or destination. - Current request handling accepts exactly one of `account_ref` or `account_code`. - `ledger_account_ref` is accepted as a legacy alias for `account_ref`. - type: object - additionalProperties: false - properties: - account_ref: - description: Reference of the primary ledger account. - type: string - ledger_account_ref: - description: Legacy alias for `account_ref`. - deprecated: true - type: string - account_code: - description: Account code of the primary ledger account. - type: string - contra_account_ref: - description: Optional contra ledger account reference for balancing entries. - type: string - contra_account_code: - description: Optional contra ledger account code for balancing entries. - type: string - - PaymentMethodEndpoint: - description: Stored payment method selector. Sent with endpoint type `paymentMethod` and `data.ref`. - type: object - additionalProperties: false - required: - - ref - properties: - ref: - description: Stored payment method reference. - type: string - - AccountEndpoint: - description: Account-owned main payment method selector. Sent with endpoint type `account` and `data.ref`. - type: object - additionalProperties: false - required: - - ref - properties: - ref: - description: Account reference whose main payment method should be used. - type: string - - RecipientEndpoint: - description: Recipient-owned main payment method selector. Sent with endpoint type `recipient` and `data.ref`. - type: object - additionalProperties: false - required: - - ref - properties: - ref: - description: Recipient reference whose main payment method should be used. - type: string - - ManagedWalletEndpoint: - description: Endpoint referencing a managed wallet within the platform. - type: object - additionalProperties: false - required: - - ref - properties: - ref: - description: Reference of the managed wallet. - type: string - asset: - description: Asset details for the managed wallet transfer. - $ref: ./payment.yaml#/components/schemas/Asset - - ExternalChainEndpoint: - description: Endpoint representing an external blockchain address. - type: object - additionalProperties: false - required: - - asset - - address - properties: - asset: - description: Asset to be transferred to or from the external address. - $ref: ./payment.yaml#/components/schemas/ExternalChainAsset - address: - description: Target blockchain address. - type: string - memo: - description: Optional destination memo or tag required by some chains. - type: string - - CardEndpoint: - description: | - Raw card payout endpoint. - Current quotation logic only requires `pan`; `exp_month` and `exp_year` are accepted but optional while pricing. - Raw-card payment execution requires `pan`, `exp_month`, and `exp_year`. - That execution rule also applies when a payment is later created from `quoteRef`, because the quoted endpoint snapshot is reused. - type: object - additionalProperties: false - required: - - pan - properties: - pan: - description: Primary account number of the card. Required for both quotation and raw-card payment execution. - type: string - firstName: - description: Optional cardholder first name hint. Current quote and execution logic do not require it. - type: string - lastName: - description: Optional cardholder last name hint. Current quote and execution logic do not require it. - type: string - exp_month: - description: Card expiration month. Optional for quotation, but required for raw-card payment execution, including payments executed later from `quoteRef`. - type: integer - format: int32 - minimum: 0 - exp_year: - description: Card expiration year. Optional for quotation, but required for raw-card payment execution, including payments executed later from `quoteRef`. - type: integer - format: int32 - minimum: 0 - country: - description: Optional card country hint. Current quote and execution logic do not require it. - type: string - - CardTokenEndpoint: - description: | - Tokenized card payout endpoint used instead of raw PAN details. - Current quotation and payment-execution logic require only `token`. - `masked_pan` remains optional and is mainly used for display, auditing, and provider-specific context. - type: object - additionalProperties: false - required: - - token - properties: - token: - description: Provider-issued card token. Required for both quotation and tokenized-card payment execution. - type: string - masked_pan: - description: Optional masked PAN representation for display and auditing. Current quote and execution logic do not require it. - type: string - - BankAccountEndpoint: - description: | - Russian domestic bank-account payout endpoint. - The recipient name is grouped under `recipient` with `first_name` - and `last_name`. - type: object - additionalProperties: false - required: - - recipient - - kpp - - bankName - - bik - - accountNumber - - correspondentAccount - properties: - recipient: - description: Legal recipient name for the bank-account payout. - type: object - additionalProperties: false - required: - - first_name - - last_name - properties: - first_name: - description: Recipient first name. - type: string - last_name: - description: Recipient last name. - type: string - inn: - description: Optional Russian INN tax identifier for the recipient. If present, it must be valid. Bank payout execution uses this value before falling back to the customer or saved owner tax identifier. - type: string - kpp: - description: Russian KPP tax registration reason code. - type: string - bankName: - description: Recipient bank name. - type: string - bik: - description: 9-digit Russian bank identification code. - type: string - accountNumber: - description: 20-digit Russian bank account number. - type: string - correspondentAccount: - description: 20-digit correspondent account number. - type: string - example: - recipient: - first_name: Ivan - last_name: Ivanov - inn: '7707083893' - kpp: '770701001' - bankName: Test Bank - bik: '044525225' - accountNumber: '40702810900000000001' - correspondentAccount: '30101810400000000225' - - IBANEndpoint: - description: International bank-account payout endpoint. - type: object - additionalProperties: false - required: - - iban - - accountHolder - properties: - iban: - type: string - accountHolder: - type: string - bic: - type: string - bankName: - type: string - - Endpoint: - description: | - Polymorphic payment endpoint definition. Every endpoint is expressed with the same - `type` plus `data` shape. Stored references are typed endpoints too: a stored payment - method uses `type: paymentMethod`, a recipient main method uses `type: recipient`, and - an account main method uses `type: account`, each with a `ref` inside `data`. - Flat top-level selector fields (`paymentMethodRef`, `recipientRef`, `accountRef`) are - not accepted; the request handler rejects them. - type: object - additionalProperties: false - required: - - type - - data - properties: - type: - description: Endpoint type determining the shape of the `data` field. - $ref: ../../external/endpoint_type.yaml#/components/schemas/EndpointType - data: - description: Endpoint payload corresponding to the selected endpoint type. For raw `card` payloads, `pan` is always required and expiry becomes required once the intent is executed. - anyOf: - - $ref: ./payment.yaml#/components/schemas/PaymentMethodEndpoint - - $ref: ./payment.yaml#/components/schemas/AccountEndpoint - - $ref: ./payment.yaml#/components/schemas/RecipientEndpoint - - $ref: ./payment.yaml#/components/schemas/LedgerEndpoint - - $ref: ./payment.yaml#/components/schemas/ManagedWalletEndpoint - - $ref: ./payment.yaml#/components/schemas/ExternalChainEndpoint - - $ref: ./payment.yaml#/components/schemas/CardEndpoint - - $ref: ./payment.yaml#/components/schemas/CardTokenEndpoint - - $ref: ./payment.yaml#/components/schemas/BankAccountEndpoint - - $ref: ./payment.yaml#/components/schemas/IBANEndpoint - metadata: - description: Optional key-value metadata attached to the endpoint. - type: object - additionalProperties: - type: string - - PaymentResponseEndpoint: - description: | - Endpoint snapshot attached to a persisted payment. Uses the same `type` plus `data` - shape as request endpoints. Stored-reference snapshots are emitted as typed endpoints - (`paymentMethod`, `account`, or `recipient`) with a `ref` inside `data`. - type: object - additionalProperties: false - required: - - type - - data - properties: - type: - description: Endpoint type for the endpoint snapshot. - $ref: ../../external/endpoint_type.yaml#/components/schemas/EndpointType - data: - description: Endpoint payload snapshot; shape depends on `type`. - anyOf: - - $ref: ./payment.yaml#/components/schemas/PaymentMethodEndpoint - - $ref: ./payment.yaml#/components/schemas/AccountEndpoint - - $ref: ./payment.yaml#/components/schemas/RecipientEndpoint - - $ref: ./payment.yaml#/components/schemas/LedgerEndpoint - - $ref: ./payment.yaml#/components/schemas/ManagedWalletEndpoint - - $ref: ./payment.yaml#/components/schemas/ExternalChainEndpoint - - $ref: ./payment.yaml#/components/schemas/CardEndpoint - - $ref: ./payment.yaml#/components/schemas/CardTokenEndpoint - - $ref: ./payment.yaml#/components/schemas/BankAccountEndpoint - - $ref: ./payment.yaml#/components/schemas/IBANEndpoint - - Customer: - description: Customer identity and address attributes for compliance and routing. - type: object - additionalProperties: false - properties: - id: - description: External or internal customer identifier. - type: string - first_name: - description: Customer first name. - type: string - middle_name: - description: Customer middle name. - type: string - last_name: - description: Customer last name. - type: string - ip: - description: Source IP address associated with the payment request. - type: string - zip: - description: Postal or ZIP code. - type: string - country: - description: Country code of customer residence or billing address. - type: string - state: - description: State or region. - type: string - city: - description: City name. - type: string - address: - description: Street address line. - type: string - tax_identifier: - description: Customer tax identifier. Russian bank-account payouts interpret this value as INN. - type: string - - CurrencyPair: - description: Foreign-exchange currency pair. - type: object - additionalProperties: false - required: - - base - - quote - properties: - base: - description: Base currency code. - type: string - quote: - description: Quote currency code. - type: string - - FxIntent: - description: FX execution preferences associated with a payment. - type: object - additionalProperties: false - required: - - pair - - side - properties: - pair: - description: Currency pair for FX conversion. - $ref: ./payment.yaml#/components/schemas/CurrencyPair - side: - description: Side of the FX trade intent. - $ref: ../../external/fx_side.yaml#/components/schemas/FxSide - firm: - description: Whether a firm quote is required. - type: boolean - ttl_ms: - description: Desired quote time-to-live in milliseconds. - type: integer - format: int64 - preferred_provider: - description: Preferred FX liquidity provider identifier. - type: string - max_age_ms: - description: Maximum accepted quote age in milliseconds. - type: integer - format: int32 - - PaymentIntent: - description: Requested payment execution parameters before processing. For raw `card` destinations, quotation accepts missing expiry but payment execution requires `exp_month` and `exp_year`. - type: object - additionalProperties: false - required: - - kind - - source - - destination - - amount - properties: - kind: - description: High-level payment kind that drives orchestration behavior. - $ref: ../../external/payment_kind.yaml#/components/schemas/PaymentKind - source: - description: Source endpoint from which funds are debited. - $ref: ./payment.yaml#/components/schemas/Endpoint - destination: - description: Destination endpoint to which funds are credited. If `type=card`, `exp_month` and `exp_year` are optional while quoting but required when the payment is executed. - $ref: ./payment.yaml#/components/schemas/Endpoint - amount: - description: Requested payment amount. - $ref: ../common/money.yaml#/components/schemas/Money - fx: - description: Optional FX preferences when conversion is needed. - $ref: ./payment.yaml#/components/schemas/FxIntent - settlement_mode: - description: Settlement strategy for the transfer. - $ref: ../../external/settlement_mode.yaml#/components/schemas/SettlementMode - fee_treatment: - description: How fees should be applied to source/destination amounts. - type: string - enum: - - unspecified - - add_to_source - - deduct_from_destination - attributes: - description: Additional key-value attributes used by downstream processors. - type: object - additionalProperties: - type: string - comment: - description: Optional free-form comment attached to the payment intent. - type: string - customer: - description: Optional customer information attached to the payment intent. - $ref: ./payment.yaml#/components/schemas/Customer - - FeeLine: - description: Single fee component included in a payment quote. - type: object - additionalProperties: false - properties: - ledgerAccountRef: - description: Ledger account reference receiving or charging this fee. - type: string - amount: - description: Monetary amount of the fee line. - $ref: ../common/money.yaml#/components/schemas/Money - lineType: - description: Fee line classification. - type: string - side: - description: Indicates whether the fee applies on debit or credit side. - type: string - meta: - description: Optional metadata for fee calculation traceability. - type: object - additionalProperties: - type: string - - QuoteFees: - description: Collection of fee lines for a quoted payment. - type: object - additionalProperties: false - properties: - lines: - description: List of individual quoted fee lines. - type: array - items: - $ref: ./payment.yaml#/components/schemas/FeeLine - - QuoteAmounts: - description: Amount breakdown returned with a payment quote. - type: object - additionalProperties: false - properties: - sourcePrincipal: - description: Principal amount debited from the source before fees. - $ref: ../common/money.yaml#/components/schemas/Money - sourceDebitTotal: - description: Total amount debited from the source including adjustments. - $ref: ../common/money.yaml#/components/schemas/Money - destinationSettlement: - description: Net amount expected to settle at the destination. - $ref: ../common/money.yaml#/components/schemas/Money - - FxQuote: - description: FX quote details used to price converted payment amounts. - type: object - additionalProperties: false - properties: - quoteRef: - description: Identifier of the FX quote. - type: string - baseCurrency: - description: Base currency of the quote pair. - type: string - quoteCurrency: - description: Quote currency of the quote pair. - type: string - side: - description: FX side used for pricing. - type: string - price: - description: Quoted FX price as a decimal string. - type: string - baseAmount: - description: Base currency amount covered by this quote. - $ref: ../common/money.yaml#/components/schemas/Money - quoteAmount: - description: Quote currency amount corresponding to the base amount. - $ref: ../common/money.yaml#/components/schemas/Money - expiresAtUnixMs: - description: Quote expiration timestamp in Unix milliseconds. - type: integer - format: int64 - pricedAtUnixMs: - description: Quote pricing timestamp in Unix milliseconds. - type: integer - format: int64 - provider: - description: Provider that supplied the FX quote. - type: string - rateRef: - description: Provider-specific rate reference identifier. - type: string - firm: - description: Indicates whether the FX quote is firm. - type: boolean - - FxRateCalculation: - description: Exchange-rate calculation used by an FX quote. - type: object - additionalProperties: false - required: - - requestedPair - - requestedSide - - pathKind - - marketRate - - effectiveRate - - overrideScope - - specialRateApplied - - legs - properties: - requestedPair: - description: Requested currency pair for the FX quote. - $ref: ./payment.yaml#/components/schemas/CurrencyPair - requestedSide: - description: FX side interpreted against the requested pair. - $ref: ../../external/fx_side.yaml#/components/schemas/FxSide - pathKind: - description: Whether the applied rate was obtained from a direct pair or a cross-rate path. - type: string - enum: - - direct - - cross - marketRate: - description: Market-path rate before quote-level overrides, as a decimal string. - type: string - effectiveRate: - description: Final rate applied to the quote after leg-level or whole-rate overrides, as a decimal string. - type: string - overrideScope: - description: Location of the rate override. `none` means no special rate was applied. - type: string - enum: - - none - - leg - - total - specialRateApplied: - description: Derived convenience flag equal to `overrideScope != none`. - type: boolean - overrideRef: - description: Opaque support/audit reference for whole-rate overrides. Clients must not parse it. - type: string - asOfUnixMs: - description: Calculation/path decision timestamp in Unix milliseconds. - type: integer - format: int64 - legs: - description: Resolved market path legs used to obtain the rate. - type: array - items: - $ref: ./payment.yaml#/components/schemas/FxRateCalculationLeg - - FxRateCalculationLeg: - description: One market-path leg used in an FX quote rate calculation. - type: object - additionalProperties: false - required: - - pair - - sourcePair - - invertedFromSource - - marketRate - - effectiveRate - - overrideApplied - properties: - pair: - description: Oriented pair multiplied in path order. - $ref: ./payment.yaml#/components/schemas/CurrencyPair - sourcePair: - description: Stored/provider pair before orientation adjustments. - $ref: ./payment.yaml#/components/schemas/CurrencyPair - invertedFromSource: - description: Whether the source pair was inverted to produce this oriented leg. - type: boolean - marketRate: - description: Market executable rate for this leg, as a decimal string. - type: string - effectiveRate: - description: Effective rate for this leg after leg-level override, as a decimal string. - type: string - provider: - description: Rate provider identifier. - type: string - rateRef: - description: Opaque support/audit reference for the source rate. Clients must not parse it. - type: string - asOfUnixMs: - description: Source rate timestamp for this leg in Unix milliseconds. - type: integer - format: int64 - overrideApplied: - description: Whether a leg-level override was applied to this leg. - type: boolean - overrideRef: - description: Opaque support/audit reference for the leg-level override. Clients must not parse it. - type: string - - SpecialRateInfo: - description: Indicates whether quotation pricing policy applied a non-market FX rate to the quote. - type: object - additionalProperties: false - required: - - applied - properties: - applied: - description: Derived from `fxRateCalculation.overrideScope != none` when `fxRateCalculation` is present. - type: boolean - - PaymentQuote: - description: Pricing result for a specific payment intent. - type: object - additionalProperties: false - required: - - executable - - state - properties: - quoteRef: - description: Unique identifier of the payment quote. - type: string - intentRef: - description: Reference to the payment intent used for quoting. - type: string - state: - description: Backend quotation execution state. `executable` means the quote can be submitted for execution. `blocked`, `indicative`, `expired`, and `unspecified` must not be executed as-is. - type: string - enum: - - unspecified - - indicative - - executable - - blocked - - expired - executable: - description: Explicit execution flag. `false` means the quote must not be sent to `/payments/by-quote` or `/payments/by-multiquote`. - type: boolean - blockReason: - description: Reason the quote is not executable when `state` is `blocked`. Omitted for executable, indicative, and expired quotes. - type: string - enum: - - route_unavailable - - limit_blocked - - risk_blocked - - insufficient_liquidity - - price_stale - - amount_too_small - - amount_too_large - amounts: - description: Amount breakdown for source and destination values. - $ref: ./payment.yaml#/components/schemas/QuoteAmounts - fees: - description: Fee breakdown included in the quote. - $ref: ./payment.yaml#/components/schemas/QuoteFees - fxQuote: - description: Associated FX quote when conversion is part of the payment. - $ref: ./payment.yaml#/components/schemas/FxQuote - fxRateCalculation: - description: Explanation of how the FX quote rate was obtained. - $ref: ./payment.yaml#/components/schemas/FxRateCalculation - specialRate: - description: Indicates whether quotation pricing policy applied a special FX rate while pricing this quote. - $ref: ./payment.yaml#/components/schemas/SpecialRateInfo - - PaymentFailureCode: - description: | - Broad machine-readable category for a payment or operation that reports a failure - condition. This field is a public category, not the orchestration service's - canonical failure code. A card payout terminated after a definitive AML denial - uses `failure_restriction`; its canonical orchestration code is `card.aml_denied`. - - The enumeration is closed and safe to use for failure handling after checking the - payment state. The state remains the lifecycle authority: `failure_restriction` - can accompany non-terminal `needs_attention` while manual review or additional - checks are pending. `failure_policy` is reserved and is not currently returned by - the API; policy blocks are reported as `failure_restriction`. - - The published cause-to-category-to-reason list is at - https://app.sendico.io/docs/en/payment-failures/ - type: string - enum: - - failure_unspecified - - failure_balance - - failure_ledger - - failure_fx - - failure_chain - - failure_fees - - failure_policy - - failure_validation - - failure_restriction - example: failure_restriction - - PaymentFailureReason: - description: | - Stable lowercase machine-readable reason token intended for client handling. A - card payout terminated after a definitive AML denial uses `aml_denied`. Detailed - AML evidence, including the matched blacklist reason, and raw gateway, provider, - or RPC messages are not exposed here. - - Unlike `failureCode`, this token set is open: compliance policy reason codes and - provider-derived reasons can add values without a contract change. Treat unknown - tokens as the category-level failure and keep a fallback message. - - Default reasons per category: `insufficient_funds`; `ledger_operation_failed`, - `ledger_operation_cancelled`, `ledger_access_violation`, - `ledger_validation_failed`; `fee_fx_conversion_failed`; `fee_posting_failed`; - `gateway_execution_failed`, `gateway_execution_rejected`, `gateway_execution_cancelled`, - `gateway_transfer_failed`, `gateway_transfer_cancelled`, `card_payout_failed`, - `card_payout_cancelled`, `provider_declined`, `provider_limit`, - `provider_unavailable`, `provider_status_unknown`, `provider_unknown`, - `velocity_limit_exceeded`, `limits_rejected`; `validation_failed`, - `quote_precision_identity_missing`, `quote_precision_asset_identity_mismatch`, - `quote_precision_asset_scale_missing`, `quote_precision_amount_scale_exceeded`, - `card_validation_failed`, `card_scheme_unsupported`, `card_expired`, - `amount_below_minimum`, `amount_above_maximum`, `duplicate_transaction`, - `currency_unsupported`; `card_scheme_disabled`, `aml_denied`, `policy_blocked`, - `review_required`, `more_checks_required`, `risk_restricted`, - `operation_restricted`, `card_product_unsupported`; `assessment_failed`; - `failure_unspecified` for an unclassified cause. - - Additional currently known override reasons are `do_not_honor`, `invalid_card`, - `invalid_card_number`, `invalid_cardholder_name`, `cardholder_name_mismatch`, - `card_details_invalid`, `card_expiry_required`, `invalid_amount`, - `provider_cancelled`, `disabled_card_scheme`, - `unsupported_gateway_card_scheme`, and `recipient_tax_identifier_required`. - Structured bank validation can add normalized reasons ending in `_required` or - `_invalid`; compliance policy reason codes are also an open set. - - The published cause-to-category-to-reason list, including the additional - provider, submit-validation, and bank-validation tokens, is at - https://app.sendico.io/docs/en/payment-failures/ - type: string - pattern: '^[a-z0-9_]+$' - example: aml_denied - - PaymentOperation: - description: | - Execution step record captured during payment processing. For a card payout - terminated by AML screening, `failureCode` is `failure_restriction` and - `failureReason` is `aml_denied`. - type: object - additionalProperties: false - properties: - stepRef: - description: Identifier of the orchestration step. - type: string - code: - description: Operation code representing the executed action. - type: string - state: - description: Current state of the operation. - type: string - label: - description: Human-readable operation label. - type: string - comment: - description: Optional comment attached to the execution step. - type: string - money: - description: Planned and executed monetary snapshots for the operation. - $ref: ./payment.yaml#/components/schemas/PaymentOperationMoney - operationRef: - description: External operation reference identifier reported by the gateway. - type: string - gateway: - description: Gateway microservice type handling the operation. - type: string - failureCode: - $ref: ./payment.yaml#/components/schemas/PaymentFailureCode - failureReason: - $ref: ./payment.yaml#/components/schemas/PaymentFailureReason - startedAt: - description: RFC 3339 timestamp when execution of the operation started. - type: string - format: date-time - completedAt: - description: RFC 3339 timestamp when execution of the operation completed. - type: string - format: date-time - - PaymentOperationMoney: - description: Planned and executed monetary snapshots associated with the operation. - type: object - additionalProperties: false - properties: - planned: - description: Monetary values planned for the operation before execution. - $ref: ./payment.yaml#/components/schemas/PaymentOperationMoneySnapshot - executed: - description: Monetary values observed after the operation was executed. - $ref: ./payment.yaml#/components/schemas/PaymentOperationMoneySnapshot - - PaymentOperationMoneySnapshot: - description: Monetary snapshot containing base and converted amounts for an operation state. - type: object - additionalProperties: false - properties: - amount: - description: Primary money amount associated with the operation snapshot. - $ref: ../common/money.yaml#/components/schemas/Money - convertedAmount: - description: Secondary amount for conversion operations in the same snapshot. - $ref: ../common/money.yaml#/components/schemas/Money - - Payment: - description: | - Persisted payment aggregate with status, quote, and operation history. A definitive - card-payout AML denial terminates the payment in the failed state and is reported as - `failureCode: failure_restriction` with `failureReason: aml_denied` at both payment - and failed-operation level. - type: object - additionalProperties: false - properties: - paymentRef: - description: Unique payment reference identifier. - type: string - state: - description: Current lifecycle state of the payment. - $ref: ../../external/payment_state.yaml#/components/schemas/PaymentState - comment: - description: Optional comment copied from the original payment intent. - type: string - source: - description: Source endpoint snapshot captured from intent. - $ref: ./payment.yaml#/components/schemas/PaymentResponseEndpoint - destination: - description: Destination endpoint snapshot captured from intent. - $ref: ./payment.yaml#/components/schemas/PaymentResponseEndpoint - failureCode: - $ref: ./payment.yaml#/components/schemas/PaymentFailureCode - failureReason: - $ref: ./payment.yaml#/components/schemas/PaymentFailureReason - operations: - description: Chronological list of operations executed for this payment. - type: array - items: - $ref: ./payment.yaml#/components/schemas/PaymentOperation - lastQuote: - description: Most recent payment quote used or generated for the payment. - $ref: ./payment.yaml#/components/schemas/PaymentQuote - createdAt: - description: RFC 3339 timestamp when the payment was created. - type: string - format: date-time - completedAt: - description: RFC 3339 timestamp when the payment reached a terminal state. Omitted while the payment is still in progress. - type: string - format: date-time - meta: - description: Additional metadata captured for the payment. - type: object - additionalProperties: - type: string diff --git a/backend/contracts/sendico/models/payment_method/payment_method.yaml b/backend/contracts/sendico/models/payment_method/payment_method.yaml deleted file mode 100644 index 7b475f0..0000000 --- a/backend/contracts/sendico/models/payment_method/payment_method.yaml +++ /dev/null @@ -1,29 +0,0 @@ -components: - schemas: - PaymentMethod: - allOf: - - $ref: ../permission_bound.yaml#/components/schemas/PermissionBound - - $ref: ../common/describable.yaml#/components/schemas/Describable - - type: object - additionalProperties: false - required: - - ownerKind - - ownerRef - - type - - isMain - properties: - ownerKind: - type: string - enum: - - ACCOUNT - - RECIPIENT - ownerRef: - $ref: ../objectid.yaml#/components/schemas/ObjectId - type: - $ref: ../../external/payment_method_type.yaml#/components/schemas/PaymentMethodType - data: - description: Payment-method-type-specific payload. `bankAccount` methods use the same Russian bank-account data shape as `BankAccountEndpoint`. - type: object - additionalProperties: true - isMain: - type: boolean diff --git a/backend/contracts/sendico/models/permission_bound.yaml b/backend/contracts/sendico/models/permission_bound.yaml deleted file mode 100644 index c574a13..0000000 --- a/backend/contracts/sendico/models/permission_bound.yaml +++ /dev/null @@ -1,23 +0,0 @@ -components: - schemas: - PermissionBound: - description: Association between an organization and a permission, including archival state. - allOf: - - $ref: ./storable.yaml#/components/schemas/Storable - - type: object - description: Permission binding details scoped to an organization. - additionalProperties: false - required: - - organizationRef - - permissionRef - - isArchived - properties: - organizationRef: - description: Identifier of the organization that owns this permission binding. - $ref: ./objectid.yaml#/components/schemas/ObjectId - permissionRef: - description: Identifier of the permission granted to the organization. - $ref: ./objectid.yaml#/components/schemas/ObjectId - isArchived: - description: Indicates whether this object is archived and inactive. - type: boolean diff --git a/backend/contracts/sendico/models/recipient/recipient.yaml b/backend/contracts/sendico/models/recipient/recipient.yaml deleted file mode 100644 index 5ab3d56..0000000 --- a/backend/contracts/sendico/models/recipient/recipient.yaml +++ /dev/null @@ -1,30 +0,0 @@ -components: - schemas: - Recipient: - allOf: - - $ref: ../permission_bound.yaml#/components/schemas/PermissionBound - - $ref: ../common/describable.yaml#/components/schemas/Describable - - type: object - additionalProperties: false - required: - - email - - status - - type - properties: - email: - type: string - format: email - avatarUrl: - type: string - nullable: true - status: - type: string - enum: - - ready - - registered - - notRegistered - type: - type: string - enum: - - internal - - external diff --git a/backend/contracts/sendico/models/storable.yaml b/backend/contracts/sendico/models/storable.yaml deleted file mode 100644 index 325b291..0000000 --- a/backend/contracts/sendico/models/storable.yaml +++ /dev/null @@ -1,22 +0,0 @@ -components: - schemas: - Storable: - description: Common persistence metadata included on stored entities. - type: object - additionalProperties: false - required: - - id - - createdAt - - updatedAt - properties: - id: - description: Unique identifier assigned to the stored entity. - $ref: ./objectid.yaml#/components/schemas/ObjectId - createdAt: - description: RFC 3339 timestamp indicating when the entity was created. - type: string - format: date-time - updatedAt: - description: RFC 3339 timestamp indicating the most recent entity update. - type: string - format: date-time diff --git a/backend/contracts/sendico/models/wallet/wallet_asset.yaml b/backend/contracts/sendico/models/wallet/wallet_asset.yaml deleted file mode 100644 index 6d1a1a6..0000000 --- a/backend/contracts/sendico/models/wallet/wallet_asset.yaml +++ /dev/null @@ -1,19 +0,0 @@ -components: - schemas: - WalletAsset: - description: Canonical managed-wallet asset identified by chain, token symbol, and optional contract address. - type: object - additionalProperties: false - required: - - chain - - tokenSymbol - properties: - chain: - description: Canonical blockchain network for the asset. - $ref: ../../external/chain_network.yaml#/components/schemas/ChainNetwork - tokenSymbol: - description: Upper-case asset symbol. - type: string - contractAddress: - description: Normalized contract address for token assets. Omitted for native assets. - type: string diff --git a/backend/edge/shared/interface/api/config.go b/backend/edge/shared/interface/api/config.go deleted file mode 100644 index ce81552..0000000 --- a/backend/edge/shared/interface/api/config.go +++ /dev/null @@ -1,15 +0,0 @@ -package api - -import ( - mwa "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" -) - -type Config struct { - Mw *mwa.Config `yaml:"middleware"` - AuthStorage *AuthStorageConfig `yaml:"auth_storage"` - Verification *VerificationConfig `yaml:"verification"` -} - -func (c *Config) SharedConfig() *Config { - return c -} diff --git a/backend/edge/shared/interface/api/srequest/login.go b/backend/edge/shared/interface/api/srequest/login.go deleted file mode 100644 index 9ae8534..0000000 --- a/backend/edge/shared/interface/api/srequest/login.go +++ /dev/null @@ -1,9 +0,0 @@ -package srequest - -import "git.sendico.io/tech/sxchnge/bff/pkg/model" - -type Login struct { - model.SessionIdentifier `json:",inline"` - model.LoginData `json:"login"` - ClientSecret string `json:"clientSecret,omitempty"` -} diff --git a/backend/edge/shared/interface/api/srequest/refresh.go b/backend/edge/shared/interface/api/srequest/refresh.go deleted file mode 100644 index cfe5d60..0000000 --- a/backend/edge/shared/interface/api/srequest/refresh.go +++ /dev/null @@ -1,5 +0,0 @@ -package srequest - -import "git.sendico.io/tech/sxchnge/bff/pkg/model" - -type AccessTokenRefresh = model.ClientRefreshToken diff --git a/backend/edge/shared/interface/api/srequest/rotate.go b/backend/edge/shared/interface/api/srequest/rotate.go deleted file mode 100644 index 0b98fd5..0000000 --- a/backend/edge/shared/interface/api/srequest/rotate.go +++ /dev/null @@ -1,5 +0,0 @@ -package srequest - -import "git.sendico.io/tech/sxchnge/bff/pkg/model" - -type TokenRefreshRotate = model.ClientRefreshToken diff --git a/backend/edge/shared/interface/api/sresponse/response.go b/backend/edge/shared/interface/api/sresponse/response.go deleted file mode 100644 index 849b790..0000000 --- a/backend/edge/shared/interface/api/sresponse/response.go +++ /dev/null @@ -1,14 +0,0 @@ -package sresponse - -import ( - "net/http" - - emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model" - "git.sendico.io/tech/sxchnge/bff/pkg/model" -) - -type ( - HandlerFunc = func(r *http.Request) http.HandlerFunc - AccountHandlerFunc = func(r *http.Request, account *model.Account, accessToken *TokenData) http.HandlerFunc - PendingAccountHandlerFunc = func(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc -) diff --git a/backend/edge/shared/server/server.go b/backend/edge/shared/server/server.go deleted file mode 100644 index 5d35149..0000000 --- a/backend/edge/shared/server/server.go +++ /dev/null @@ -1,12 +0,0 @@ -package server - -import ( - edgeapi "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - internalserver "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/server" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/server" -) - -func Create(logger mlogger.Logger, file string, debug bool, runtime edgeapi.RuntimeConfig) (server.Application, error) { - return internalserver.Create(logger, file, debug, runtime) -} diff --git a/backend/edge/shared/storage/refreshtokens/refreshtokens.go b/backend/edge/shared/storage/refreshtokens/refreshtokens.go deleted file mode 100644 index 4fc018a..0000000 --- a/backend/edge/shared/storage/refreshtokens/refreshtokens.go +++ /dev/null @@ -1,19 +0,0 @@ -package refreshtokens - -import ( - "context" - - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "go.mongodb.org/mongo-driver/v2/bson" -) - -type DB interface { - template.DB[*model.RefreshToken] - Revoke(ctx context.Context, accountRef bson.ObjectID, session *model.SessionIdentifier) error - RevokeToken(ctx context.Context, token *model.ClientRefreshToken) (bool, error) - RevokeAll(ctx context.Context, accountRef bson.ObjectID, deviceID string) error - GetSession(ctx context.Context, accountRef bson.ObjectID, session *model.SessionIdentifier) (*model.RefreshToken, error) - GetByCRT(ctx context.Context, t *model.ClientRefreshToken) (*model.RefreshToken, error) - GetClient(ctx context.Context, clientID string) (*model.Client, error) -} diff --git a/backend/edge/shared/storage/verification/context_ref.go b/backend/edge/shared/storage/verification/context_ref.go deleted file mode 100644 index ef4b029..0000000 --- a/backend/edge/shared/storage/verification/context_ref.go +++ /dev/null @@ -1,22 +0,0 @@ -package verification - -import ( - "strings" - - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/model" -) - -func normalizeContextRef(purpose model.VerificationPurpose, contextRef string) (string, error) { - _ = purpose - return strings.TrimSpace(contextRef), nil -} - -func appendContextRefFilter(filters []builder.Query, purpose model.VerificationPurpose, contextRef string) []builder.Query { - _ = purpose - if strings.TrimSpace(contextRef) != "" { - filters = append(filters, repository.Filter("contextRef", strings.TrimSpace(contextRef))) - } - return filters -} diff --git a/backend/internal/architecture/contracts_test.go b/backend/internal/architecture/contracts_test.go deleted file mode 100644 index dab6966..0000000 --- a/backend/internal/architecture/contracts_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package architecture - -import ( - "gopkg.in/yaml.v3" - "io/fs" - "os" - "path/filepath" - "strings" - "testing" -) - -func TestPublicContractReferencesAreSelfContained(t *testing.T) { - root := filepath.Join("..", "..", "contracts", "sendico") - err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() || filepath.Ext(path) != ".yaml" { - return nil - } - data, err := os.ReadFile(path) - if err != nil { - return err - } - var doc yaml.Node - if err := yaml.Unmarshal(data, &doc); err != nil { - return err - } - var visit func(*yaml.Node) - visit = func(n *yaml.Node) { - if n.Kind == yaml.MappingNode { - for i := 0; i+1 < len(n.Content); i += 2 { - if n.Content[i].Value == "$ref" { - ref := strings.SplitN(n.Content[i+1].Value, "#", 2)[0] - if ref == "" { - continue - } - target := filepath.Clean(filepath.Join(filepath.Dir(path), ref)) - relative, err := filepath.Rel(root, target) - if err != nil || strings.HasPrefix(relative, "..") || strings.Contains(ref, "://") { - t.Errorf("%s: nonlocal reference %s", path, ref) - continue - } - if _, err := os.Stat(target); err != nil { - t.Errorf("%s: missing reference %s", path, ref) - } - } - } - } - for _, child := range n.Content { - visit(child) - } - } - visit(&doc) - return nil - }) - if err != nil { - t.Fatal(err) - } -} diff --git a/backend/pkg/db/account/account.go b/backend/pkg/db/account/account.go deleted file mode 100644 index 369b04f..0000000 --- a/backend/pkg/db/account/account.go +++ /dev/null @@ -1,14 +0,0 @@ -package account - -import ( - "context" - - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/model" -) - -// DB is the interface which must be implemented by all db drivers -type DB interface { - template.DB[*model.Account] - GetByEmail(ctx context.Context, email string) (*model.Account, error) -} diff --git a/backend/pkg/db/config.go b/backend/pkg/db/config.go deleted file mode 100644 index 52d626d..0000000 --- a/backend/pkg/db/config.go +++ /dev/null @@ -1,13 +0,0 @@ -package db - -import ( - "git.sendico.io/tech/sxchnge/bff/pkg/model" -) - -type DBDriver string - -const ( - Mongo DBDriver = "mongodb" -) - -type Config = model.DriverConfig[DBDriver] diff --git a/backend/pkg/db/internal/mongo/accountdb/user.go b/backend/pkg/db/internal/mongo/accountdb/user.go deleted file mode 100644 index 76f9697..0000000 --- a/backend/pkg/db/internal/mongo/accountdb/user.go +++ /dev/null @@ -1,13 +0,0 @@ -package accountdb - -import ( - "context" - - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/model" -) - -func (db *AccountDB) GetByEmail(ctx context.Context, email string) (*model.Account, error) { - var account model.Account - return &account, db.FindOne(ctx, repository.Filter("login", email), &account) -} diff --git a/backend/pkg/mlogger/factory/mlogger.go b/backend/pkg/mlogger/factory/mlogger.go deleted file mode 100644 index 0f8a709..0000000 --- a/backend/pkg/mlogger/factory/mlogger.go +++ /dev/null @@ -1,10 +0,0 @@ -package mlogger - -import ( - lt "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - li "git.sendico.io/tech/sxchnge/bff/pkg/mlogger/internal/mlogger" -) - -func NewLogger(debug bool) lt.Logger { - return li.NewLoggerImp(debug) -} diff --git a/backend/pkg/version/factory/factory.go b/backend/pkg/version/factory/factory.go deleted file mode 100644 index d805562..0000000 --- a/backend/pkg/version/factory/factory.go +++ /dev/null @@ -1,10 +0,0 @@ -package version - -import ( - "git.sendico.io/tech/sxchnge/bff/pkg/version" - versionimp "git.sendico.io/tech/sxchnge/bff/pkg/version/internal" -) - -func Create(info *version.Info) version.Printer { - return versionimp.Create(info) -} diff --git a/backend/product/README.md b/backend/product/README.md new file mode 100644 index 0000000..96f0039 --- /dev/null +++ b/backend/product/README.md @@ -0,0 +1,38 @@ +# SXCHNGE product BFF + +This Go module is the customer-facing executable. `main.go` composes the copied +runtime and local customer service. Customers, auth, sessions and verification +own their models and supporting code; generic infrastructure comes from the +sibling `shared` library module. + +## Run + +Requires Go 1.27 and an SXCHNGE-owned MongoDB replica set for transactions. +Copy `config.example.yml` to an untracked `config.yml` in this directory. Set: + +- `SXCHNGE_MONGO_URI`: URI for SXCHNGE's database. +- `SXCHNGE_JWT_SECRET`: random signing key, at least 32 bytes for HS256. +- `SXCHNGE_DOMAIN`: local hostname, such as `localhost:8082`. +- `SXCHNGE_API_PROTOCOL`: `http` locally, `https` in deployment. +- `SXCHNGE_API_ENDPOINT`: `/api/v1`. + +From the repository root, run `make run-backend`, or run `go run .` here. +Flags: `-config.file`, `-version`, `-debug`. Configure exact browser origins in +YAML; browser refresh cookies require HTTPS. An empty local database needs no +organization, role, policy or seed; the runtime creates its indexes. + +The runtime mounts health, login, API login, refresh, rotation and logout routes. +Customer/application-client records must exist for login. The customer service +supports atomic account/activation creation and deletion with session revocation. +Signup/profile HTTP wiring and verification delivery/completion remain follow-up +work. The frontend still uses dummy services. + +Sendico integration is not wired. Its organization context belongs only to the +future public HTTP adapter. There is no unused public API snapshot in this module; +copy only the wire definitions consumed by an implemented adapter. + +## Validate + +`go test -race ./...` and `go vet ./...` validate this module. Tests require Docker +for disposable Mongo replica sets. Root `make test` and `make lint-backend` also +validate `shared`. See [source provenance](../PROVENANCE.md). diff --git a/backend/edge/shared/interface/api/api.go b/backend/product/app/api/api.go similarity index 65% rename from backend/edge/shared/interface/api/api.go rename to backend/product/app/api/api.go index 480518d..8e156a4 100644 --- a/backend/edge/shared/interface/api/api.go +++ b/backend/product/app/api/api.go @@ -3,10 +3,10 @@ package api import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/domainprovider" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/app/urls" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" ) type API interface { diff --git a/backend/product/app/api/config.go b/backend/product/app/api/config.go new file mode 100644 index 0000000..78b650c --- /dev/null +++ b/backend/product/app/api/config.go @@ -0,0 +1,16 @@ +package api + +import ( + mwa "git.sendico.io/tech/sxchnge/product/auth/config" + verificationconfig "git.sendico.io/tech/sxchnge/product/verification/config" +) + +type Config struct { + Mw *mwa.Config `yaml:"middleware"` + AuthStorage *mwa.AuthStorageConfig `yaml:"auth_storage"` + Verification *verificationconfig.Config `yaml:"verification"` +} + +func (c *Config) SharedConfig() *Config { + return c +} diff --git a/backend/edge/shared/interface/api/register.go b/backend/product/app/api/register.go similarity index 70% rename from backend/edge/shared/interface/api/register.go rename to backend/product/app/api/register.go index d20e924..4361edd 100644 --- a/backend/edge/shared/interface/api/register.go +++ b/backend/product/app/api/register.go @@ -1,9 +1,9 @@ package api import ( - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/response" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/service" ) type Register interface { diff --git a/backend/edge/shared/interface/api/runtime.go b/backend/product/app/api/runtime.go similarity index 93% rename from backend/edge/shared/interface/api/runtime.go rename to backend/product/app/api/runtime.go index 439b4fb..1490caf 100644 --- a/backend/edge/shared/interface/api/runtime.go +++ b/backend/product/app/api/runtime.go @@ -1,6 +1,6 @@ package api -import "git.sendico.io/tech/sxchnge/bff/pkg/mservice" +import "git.sendico.io/tech/sxchnge/shared/service" type ConfigProvider interface { SharedConfig() *Config diff --git a/backend/pkg/api/routers/health.go b/backend/product/app/health/health.go similarity index 67% rename from backend/pkg/api/routers/health.go rename to backend/product/app/health/health.go index c03e723..ba328d2 100644 --- a/backend/pkg/api/routers/health.go +++ b/backend/product/app/health/health.go @@ -1,9 +1,9 @@ package routers import ( - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/internal/healthimp" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/product/app/health/internal" + "git.sendico.io/tech/sxchnge/product/app/health/status" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/go-chi/chi/v5" ) diff --git a/backend/pkg/api/routers/internal/healthimp/health.go b/backend/product/app/health/internal/health.go similarity index 94% rename from backend/pkg/api/routers/internal/healthimp/health.go rename to backend/product/app/health/internal/health.go index 86ddb5d..c596af2 100644 --- a/backend/pkg/api/routers/internal/healthimp/health.go +++ b/backend/product/app/health/internal/health.go @@ -4,8 +4,8 @@ import ( "net/http" "strings" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/product/app/health/status" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) diff --git a/backend/pkg/api/routers/internal/healthimp/health_test.go b/backend/product/app/health/internal/health_test.go similarity index 96% rename from backend/pkg/api/routers/internal/healthimp/health_test.go rename to backend/product/app/health/internal/health_test.go index 038f860..e0b5f01 100644 --- a/backend/pkg/api/routers/internal/healthimp/health_test.go +++ b/backend/product/app/health/internal/health_test.go @@ -3,7 +3,7 @@ package healthimp import ( "testing" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" + "git.sendico.io/tech/sxchnge/product/app/health/status" "github.com/go-chi/chi/v5" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" diff --git a/backend/pkg/api/routers/internal/healthimp/status.go b/backend/product/app/health/internal/status.go similarity index 83% rename from backend/pkg/api/routers/internal/healthimp/status.go rename to backend/product/app/health/internal/status.go index 6ca268f..3e8099c 100644 --- a/backend/pkg/api/routers/internal/healthimp/status.go +++ b/backend/product/app/health/internal/status.go @@ -4,9 +4,9 @@ import ( "net/http" "sync" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/product/app/health/status" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" ) type Status struct { diff --git a/backend/pkg/api/routers/health/status.go b/backend/product/app/health/status/status.go similarity index 100% rename from backend/pkg/api/routers/health/status.go rename to backend/product/app/health/status/status.go diff --git a/backend/edge/shared/internal/transport/http/middleware.go b/backend/product/app/http/middleware.go similarity index 84% rename from backend/edge/shared/internal/transport/http/middleware.go rename to backend/product/app/http/middleware.go index 6e8a502..c4c7c9d 100644 --- a/backend/edge/shared/internal/transport/http/middleware.go +++ b/backend/product/app/http/middleware.go @@ -5,19 +5,18 @@ import ( "os" "path" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - authstorage "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/storage" - edgeapi "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - httprouter "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router" - mr "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router/metrics" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + mr "git.sendico.io/tech/sxchnge/product/app/metrics" + "git.sendico.io/tech/sxchnge/product/auth/config" + httprouter "git.sendico.io/tech/sxchnge/product/auth/http" + "git.sendico.io/tech/sxchnge/product/auth/response" + authstorage "git.sendico.io/tech/sxchnge/product/auth/storage" + "git.sendico.io/tech/sxchnge/product/sessions/browser" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/errors" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" cm "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" @@ -106,7 +105,7 @@ func CreateMiddleware( db db.Factory, apiRouter *chi.Mux, config *middleware.Config, - authStorage *edgeapi.AuthStorageConfig, + authStorage *middleware.AuthStorageConfig, debug bool, ) (*Middleware, error) { if config == nil { diff --git a/backend/edge/shared/internal/transport/http/middleware_test.go b/backend/product/app/http/middleware_test.go similarity index 98% rename from backend/edge/shared/internal/transport/http/middleware_test.go rename to backend/product/app/http/middleware_test.go index 9b5a6db..776367a 100644 --- a/backend/edge/shared/internal/transport/http/middleware_test.go +++ b/backend/product/app/http/middleware_test.go @@ -6,8 +6,8 @@ import ( "net/http/httptest" "testing" - amr "git.sendico.io/tech/sxchnge/bff/pkg/api/routers" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" + amr "git.sendico.io/tech/sxchnge/product/app/health" + "git.sendico.io/tech/sxchnge/product/app/health/status" "github.com/go-chi/chi/v5" chimetrics "github.com/go-chi/metrics" "github.com/prometheus/client_golang/prometheus" diff --git a/backend/edge/shared/internal/transport/http/router/metrics/handler.go b/backend/product/app/metrics/handler.go similarity index 94% rename from backend/edge/shared/internal/transport/http/router/metrics/handler.go rename to backend/product/app/metrics/handler.go index d204243..902d3a7 100644 --- a/backend/edge/shared/internal/transport/http/router/metrics/handler.go +++ b/backend/product/app/metrics/handler.go @@ -3,7 +3,7 @@ package metrics import ( "net/http" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/go-chi/chi/v5" "github.com/go-chi/metrics" "go.uber.org/zap" diff --git a/backend/edge/shared/internal/transport/http/router/metrics/router.go b/backend/product/app/metrics/router.go similarity index 84% rename from backend/edge/shared/internal/transport/http/router/metrics/router.go rename to backend/product/app/metrics/router.go index 254071e..12d2c96 100644 --- a/backend/edge/shared/internal/transport/http/router/metrics/router.go +++ b/backend/product/app/metrics/router.go @@ -1,7 +1,7 @@ package metrics import ( - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/go-chi/chi/v5" ) diff --git a/backend/pkg/server/factory.go b/backend/product/app/process/factory.go similarity index 68% rename from backend/pkg/server/factory.go rename to backend/product/app/process/factory.go index fe572db..b605361 100644 --- a/backend/pkg/server/factory.go +++ b/backend/product/app/process/factory.go @@ -1,5 +1,5 @@ package server -import "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" +import "git.sendico.io/tech/sxchnge/shared/logging" type ServerFactoryT = func(logger mlogger.Logger, file string, debug bool) (Application, error) diff --git a/backend/pkg/server/internal/instance.go b/backend/product/app/process/internal/instance.go similarity index 88% rename from backend/pkg/server/internal/instance.go rename to backend/product/app/process/internal/instance.go index 318dc52..8dd5cb5 100644 --- a/backend/pkg/server/internal/instance.go +++ b/backend/product/app/process/internal/instance.go @@ -1,8 +1,8 @@ package serverimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/server" + "git.sendico.io/tech/sxchnge/product/app/process" + "git.sendico.io/tech/sxchnge/shared/logging" "go.uber.org/zap" ) diff --git a/backend/pkg/server/internal/server.go b/backend/product/app/process/internal/server.go similarity index 88% rename from backend/pkg/server/internal/server.go rename to backend/product/app/process/internal/server.go index a190f56..0adc273 100644 --- a/backend/pkg/server/internal/server.go +++ b/backend/product/app/process/internal/server.go @@ -7,10 +7,10 @@ import ( "os/signal" "syscall" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - lf "git.sendico.io/tech/sxchnge/bff/pkg/mlogger/factory" - "git.sendico.io/tech/sxchnge/bff/pkg/server" - "git.sendico.io/tech/sxchnge/bff/pkg/version" + "git.sendico.io/tech/sxchnge/product/app/process" + "git.sendico.io/tech/sxchnge/shared/logging" + lf "git.sendico.io/tech/sxchnge/shared/logging/factory" + "git.sendico.io/tech/sxchnge/shared/version" "github.com/google/uuid" "go.uber.org/zap" ) diff --git a/backend/pkg/server/main/run.go b/backend/product/app/process/run/run.go similarity index 51% rename from backend/pkg/server/main/run.go rename to backend/product/app/process/run/run.go index 483a7da..85066d6 100644 --- a/backend/pkg/server/main/run.go +++ b/backend/product/app/process/run/run.go @@ -1,9 +1,9 @@ package server import ( - sd "git.sendico.io/tech/sxchnge/bff/pkg/server" - serverimp "git.sendico.io/tech/sxchnge/bff/pkg/server/internal" - "git.sendico.io/tech/sxchnge/bff/pkg/version" + sd "git.sendico.io/tech/sxchnge/product/app/process" + serverimp "git.sendico.io/tech/sxchnge/product/app/process/internal" + "git.sendico.io/tech/sxchnge/shared/version" ) func RunServer(rootLoggerName string, av version.Printer, factory sd.ServerFactoryT) error { diff --git a/backend/pkg/server/server.go b/backend/product/app/process/server.go similarity index 100% rename from backend/pkg/server/server.go rename to backend/product/app/process/server.go diff --git a/backend/internal/profile/runtime.go b/backend/product/app/profile/runtime.go similarity index 79% rename from backend/internal/profile/runtime.go rename to backend/product/app/profile/runtime.go index 119749f..b8bbba1 100644 --- a/backend/internal/profile/runtime.go +++ b/backend/product/app/profile/runtime.go @@ -2,8 +2,8 @@ package profile import ( - "git.sendico.io/tech/sxchnge/bff/edge/product/interface/accountservice" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" + "git.sendico.io/tech/sxchnge/product/app/api" + "git.sendico.io/tech/sxchnge/product/customers" ) const Accounts = "accounts" diff --git a/backend/edge/shared/internal/runtime/api.go b/backend/product/app/runtime/api.go similarity index 74% rename from backend/edge/shared/internal/runtime/api.go rename to backend/product/app/runtime/api.go index e20747b..9b82f60 100644 --- a/backend/edge/shared/internal/runtime/api.go +++ b/backend/product/app/runtime/api.go @@ -3,13 +3,13 @@ package runtime import ( "context" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - httptransport "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http" - amr "git.sendico.io/tech/sxchnge/bff/pkg/api/routers" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/domainprovider" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/app/api" + amr "git.sendico.io/tech/sxchnge/product/app/health" + httptransport "git.sendico.io/tech/sxchnge/product/app/http" + "git.sendico.io/tech/sxchnge/product/app/urls" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" ) type Microservices = []mservice.MicroService diff --git a/backend/edge/shared/internal/runtime/bootstrap.go b/backend/product/app/runtime/bootstrap.go similarity index 86% rename from backend/edge/shared/internal/runtime/bootstrap.go rename to backend/product/app/runtime/bootstrap.go index bdc136b..01eb05a 100644 --- a/backend/edge/shared/internal/runtime/bootstrap.go +++ b/backend/product/app/runtime/bootstrap.go @@ -4,13 +4,13 @@ import ( "context" "os" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - httptransport "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http" - amr "git.sendico.io/tech/sxchnge/bff/pkg/api/routers" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/domainprovider" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/app/api" + amr "git.sendico.io/tech/sxchnge/product/app/health" + httptransport "git.sendico.io/tech/sxchnge/product/app/http" + "git.sendico.io/tech/sxchnge/product/app/urls" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/runtime/bootstrap_test.go b/backend/product/app/runtime/bootstrap_test.go similarity index 83% rename from backend/edge/shared/internal/runtime/bootstrap_test.go rename to backend/product/app/runtime/bootstrap_test.go index 91b76f9..43a4ca9 100644 --- a/backend/edge/shared/internal/runtime/bootstrap_test.go +++ b/backend/product/app/runtime/bootstrap_test.go @@ -2,6 +2,7 @@ package runtime_test import ( "context" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" "net/http" "net/http/httptest" "net/url" @@ -9,11 +10,10 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - sharedruntime "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/runtime" - "git.sendico.io/tech/sxchnge/bff/internal/profile" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/app/api" + "git.sendico.io/tech/sxchnge/product/app/profile" + sharedruntime "git.sendico.io/tech/sxchnge/product/app/runtime" + "git.sendico.io/tech/sxchnge/shared/database" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/modules/mongodb" @@ -35,10 +35,10 @@ func TestProfileStartsOnEmptyDatabase(t *testing.T) { query.Set("directConnection", "true") endpoint.RawQuery = query.Encode() uri = endpoint.String() - factory, err := db.NewConnection(zap.NewNop(), &db.Config{Driver: db.Mongo, Settings: model.SettingsT{"uri": uri, "database": "sxchnge_runtime"}}) + factory, err := db.NewConnection(zap.NewNop(), &db.Config{Driver: db.Mongo, Settings: sharedmodel.SettingsT{"uri": uri, "database": "sxchnge_runtime"}}) require.NoError(t, err) t.Cleanup(factory.CloseConnection) - data, err := os.ReadFile("../../../../config.example.yml") + data, err := os.ReadFile("../../config.example.yml") require.NoError(t, err) var example struct { API api.Config `yaml:"api"` diff --git a/backend/edge/shared/internal/runtime/lifecycle.go b/backend/product/app/runtime/lifecycle.go similarity index 93% rename from backend/edge/shared/internal/runtime/lifecycle.go rename to backend/product/app/runtime/lifecycle.go index 9a331b2..141c483 100644 --- a/backend/edge/shared/internal/runtime/lifecycle.go +++ b/backend/product/app/runtime/lifecycle.go @@ -3,7 +3,7 @@ package runtime import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" + "git.sendico.io/tech/sxchnge/product/app/health/status" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/runtime/services.go b/backend/product/app/runtime/services.go similarity index 79% rename from backend/edge/shared/internal/runtime/services.go rename to backend/product/app/runtime/services.go index bc3748c..d0d9ef7 100644 --- a/backend/edge/shared/internal/runtime/services.go +++ b/backend/product/app/runtime/services.go @@ -1,10 +1,10 @@ package runtime import ( - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - "git.sendico.io/tech/sxchnge/bff/pkg/api/routers/health" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/app/api" + "git.sendico.io/tech/sxchnge/product/app/health/status" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/server/config_test.go b/backend/product/app/server/internal/config_test.go similarity index 84% rename from backend/edge/shared/internal/server/config_test.go rename to backend/product/app/server/internal/config_test.go index 88eab9d..e2d4e8e 100644 --- a/backend/edge/shared/internal/server/config_test.go +++ b/backend/product/app/server/internal/config_test.go @@ -2,8 +2,8 @@ package server import ( "errors" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/product/app/api" + "git.sendico.io/tech/sxchnge/shared/errors" "os" "testing" ) @@ -17,7 +17,7 @@ func TestIncompleteConfigReturnsProjectError(t *testing.T) { } } func TestExampleConfigMatchesCopiedRuntime(t *testing.T) { - data, err := os.ReadFile("../../../../config.example.yml") + data, err := os.ReadFile("../../../config.example.yml") if err != nil { t.Fatal(err) } diff --git a/backend/edge/shared/internal/server/server.go b/backend/product/app/server/internal/server.go similarity index 92% rename from backend/edge/shared/internal/server/server.go rename to backend/product/app/server/internal/server.go index f62da2a..a1821ae 100644 --- a/backend/edge/shared/internal/server/server.go +++ b/backend/product/app/server/internal/server.go @@ -7,13 +7,13 @@ import ( "os" "time" - ac "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - sharedruntime "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/runtime" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" - mduration "git.sendico.io/tech/sxchnge/bff/pkg/mutil/duration" + ac "git.sendico.io/tech/sxchnge/product/app/api" + sharedruntime "git.sendico.io/tech/sxchnge/product/app/runtime" + "git.sendico.io/tech/sxchnge/shared/database" + mduration "git.sendico.io/tech/sxchnge/shared/duration" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" "go.uber.org/zap" "gopkg.in/yaml.v3" diff --git a/backend/edge/shared/internal/server/server_test.go b/backend/product/app/server/internal/server_test.go similarity index 88% rename from backend/edge/shared/internal/server/server_test.go rename to backend/product/app/server/internal/server_test.go index 641780d..790305c 100644 --- a/backend/edge/shared/internal/server/server_test.go +++ b/backend/product/app/server/internal/server_test.go @@ -5,7 +5,7 @@ import ( "errors" "testing" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" + "git.sendico.io/tech/sxchnge/product/app/api" "go.uber.org/zap" ) diff --git a/backend/product/app/server/server.go b/backend/product/app/server/server.go new file mode 100644 index 0000000..1ba0501 --- /dev/null +++ b/backend/product/app/server/server.go @@ -0,0 +1,12 @@ +package server + +import ( + edgeapi "git.sendico.io/tech/sxchnge/product/app/api" + "git.sendico.io/tech/sxchnge/product/app/process" + internalserver "git.sendico.io/tech/sxchnge/product/app/server/internal" + "git.sendico.io/tech/sxchnge/shared/logging" +) + +func Create(logger mlogger.Logger, file string, debug bool, runtime edgeapi.RuntimeConfig) (server.Application, error) { + return internalserver.Create(logger, file, debug, runtime) +} diff --git a/backend/pkg/domainprovider/domain_provider.go b/backend/product/app/urls/domain_provider.go similarity index 76% rename from backend/pkg/domainprovider/domain_provider.go rename to backend/product/app/urls/domain_provider.go index cd92f13..ca5eab4 100644 --- a/backend/pkg/domainprovider/domain_provider.go +++ b/backend/product/app/urls/domain_provider.go @@ -1,8 +1,8 @@ package domainprovider import ( - dimp "git.sendico.io/tech/sxchnge/bff/pkg/domainprovider/imp" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + dimp "git.sendico.io/tech/sxchnge/product/app/urls/internal" + "git.sendico.io/tech/sxchnge/shared/logging" ) type DomainProvider interface { diff --git a/backend/pkg/domainprovider/imp/domain_provider.go b/backend/product/app/urls/internal/domain_provider.go similarity index 95% rename from backend/pkg/domainprovider/imp/domain_provider.go rename to backend/product/app/urls/internal/domain_provider.go index 312485b..0c1bc70 100644 --- a/backend/pkg/domainprovider/imp/domain_provider.go +++ b/backend/product/app/urls/internal/domain_provider.go @@ -4,7 +4,7 @@ import ( "net/url" "os" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/logging" "go.uber.org/zap" ) diff --git a/backend/edge/shared/appversion/version.go b/backend/product/app/version/version.go similarity index 79% rename from backend/edge/shared/appversion/version.go rename to backend/product/app/version/version.go index 6fcdbb3..8d2af5b 100644 --- a/backend/edge/shared/appversion/version.go +++ b/backend/product/app/version/version.go @@ -1,8 +1,8 @@ package appversion import ( - "git.sendico.io/tech/sxchnge/bff/pkg/version" - vf "git.sendico.io/tech/sxchnge/bff/pkg/version/factory" + "git.sendico.io/tech/sxchnge/shared/version" + vf "git.sendico.io/tech/sxchnge/shared/version/factory" ) // Build information. Populated at build-time. diff --git a/backend/internal/architecture/boundary_test.go b/backend/product/architecture/boundary_test.go similarity index 80% rename from backend/internal/architecture/boundary_test.go rename to backend/product/architecture/boundary_test.go index 6e552f0..ecbac73 100644 --- a/backend/internal/architecture/boundary_test.go +++ b/backend/product/architecture/boundary_test.go @@ -28,6 +28,9 @@ func TestNoSendicoRuntimeOrPrivateTransportImports(t *testing.T) { if err != nil { return err } + if strings.HasPrefix(filepath.ToSlash(path), "../../shared/") && strings.HasPrefix(name, "git.sendico.io/tech/sxchnge/") && !strings.HasPrefix(name, "git.sendico.io/tech/sxchnge/shared/") { + t.Errorf("shared library imports an executable module: %s in %s", name, path) + } for _, blocked := range []string{"github.com/tech/sendico/", "google.golang.org/grpc", "github.com/nats-io/", "/pkg/discovery", "/pkg/proto/", "github.com/casbin/", "/pkg/auth", "/storage/organization", "/mongo/policiesdb", "/mongo/rolesdb", "/mongo/invitationdb"} { if strings.Contains(name, blocked) { t.Errorf("%s imports private provider transport or shared runtime: %s", path, name) diff --git a/backend/internal/architecture/customer_test.go b/backend/product/architecture/customer_test.go similarity index 89% rename from backend/internal/architecture/customer_test.go rename to backend/product/architecture/customer_test.go index 594627c..19dd588 100644 --- a/backend/internal/architecture/customer_test.go +++ b/backend/product/architecture/customer_test.go @@ -11,8 +11,7 @@ import ( ) func TestLocalCustomerModelHasNoPlatformTenancy(t *testing.T) { - // Provider organization fields are allowed in the isolated public wire snapshot, - // never in the local customer model, stores or service contracts. + // Local customer models and storage must stay free of provider tenancy. forbidden := map[string]bool{"organization": true, "organizationref": true, "orgref": true, "orgdb": true, "role": true, "roles": true, "roledescription": true, "rolemanager": true, "policy": true, "policies": true, "policydescription": true, "policydb": true, "permissionbound": true, "signup_provisioning": true} err := filepath.WalkDir("../..", func(path string, entry fs.DirEntry, err error) error { if err != nil { diff --git a/backend/internal/architecture/errors_test.go b/backend/product/architecture/errors_test.go similarity index 95% rename from backend/internal/architecture/errors_test.go rename to backend/product/architecture/errors_test.go index 56a6edb..2642768 100644 --- a/backend/internal/architecture/errors_test.go +++ b/backend/product/architecture/errors_test.go @@ -57,7 +57,7 @@ func TestErrorConstructorsUseProjectModel(t *testing.T) { return true } if (imports[ident.Name] == "errors" && sel.Sel.Name == "New") || (imports[ident.Name] == "fmt" && sel.Sel.Name == "Errorf") { - t.Errorf("%s: use backend/pkg/merrors", fset.Position(call.Pos())) + t.Errorf("%s: use backend/shared/errors", fset.Position(call.Pos())) } return true }) diff --git a/backend/edge/shared/auth/client/client.go b/backend/product/auth/client/client.go similarity index 69% rename from backend/edge/shared/auth/client/client.go rename to backend/product/auth/client/client.go index 7b7f395..fc9467d 100644 --- a/backend/edge/shared/auth/client/client.go +++ b/backend/product/auth/client/client.go @@ -3,16 +3,16 @@ package client import ( "context" "errors" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "strings" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/shared/errors" ) var ErrUnknownClientID = merrors.Define("unknown client id") -func Resolve(ctx context.Context, rtdb refreshtokens.DB, clientID string) (*model.Client, error) { +func Resolve(ctx context.Context, rtdb refreshtokens.DB, clientID string) (*sessionmodel.Client, error) { clientID = strings.TrimSpace(clientID) if clientID == "" { return nil, ErrUnknownClientID diff --git a/backend/edge/shared/auth/client/client_test.go b/backend/product/auth/client/client_test.go similarity index 81% rename from backend/edge/shared/auth/client/client_test.go rename to backend/product/auth/client/client_test.go index dfdc8bb..6f40c0c 100644 --- a/backend/edge/shared/auth/client/client_test.go +++ b/backend/product/auth/client/client_test.go @@ -3,20 +3,20 @@ package client import ( "context" "errors" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "testing" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/sessions/store" ) type resolveClientDB struct { refreshtokens.DB calls int - client *model.Client + client *sessionmodel.Client err error } -func (db *resolveClientDB) GetClient(ctx context.Context, clientID string) (*model.Client, error) { +func (db *resolveClientDB) GetClient(ctx context.Context, clientID string) (*sessionmodel.Client, error) { db.calls++ return db.client, db.err } @@ -37,7 +37,7 @@ func TestResolveRejectsBlankClientIDWithoutStorageLookup(t *testing.T) { } func TestResolveTrimsClientID(t *testing.T) { - want := &model.Client{ClientID: "client-1"} + want := &sessionmodel.Client{ClientID: "client-1"} db := &resolveClientDB{client: want} client, err := Resolve(context.Background(), db, " client-1 ") diff --git a/backend/edge/shared/interface/api/auth_config.go b/backend/product/auth/config/auth_config.go similarity index 86% rename from backend/edge/shared/interface/api/auth_config.go rename to backend/product/auth/config/auth_config.go index e155092..76ca98f 100644 --- a/backend/edge/shared/interface/api/auth_config.go +++ b/backend/product/auth/config/auth_config.go @@ -1,4 +1,4 @@ -package api +package middleware type AuthStorageConfig struct { AccountsCollection string `yaml:"accounts_collection"` @@ -34,8 +34,3 @@ func (c *AuthStorageConfig) WithDefaults() AuthStorageConfig { } return res } - -type VerificationConfig struct { - AllowedPurposes []string `yaml:"allowed_purposes"` - RateLimits *VerificationRateLimitsConfig `yaml:"rate_limits"` -} diff --git a/backend/edge/shared/interface/middleware/cors_test.go b/backend/product/auth/config/cors_test.go similarity index 100% rename from backend/edge/shared/interface/middleware/cors_test.go rename to backend/product/auth/config/cors_test.go diff --git a/backend/edge/shared/interface/middleware/login_rate_limit.go b/backend/product/auth/config/login_rate_limit.go similarity index 94% rename from backend/edge/shared/interface/middleware/login_rate_limit.go rename to backend/product/auth/config/login_rate_limit.go index 705958a..b828670 100644 --- a/backend/edge/shared/interface/middleware/login_rate_limit.go +++ b/backend/product/auth/config/login_rate_limit.go @@ -3,7 +3,7 @@ package middleware import ( "time" - sharedlimit "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/ratelimit" + sharedlimit "git.sendico.io/tech/sxchnge/shared/ratelimit" ) const ( diff --git a/backend/edge/shared/interface/middleware/login_rate_limit_test.go b/backend/product/auth/config/login_rate_limit_test.go similarity index 97% rename from backend/edge/shared/interface/middleware/login_rate_limit_test.go rename to backend/product/auth/config/login_rate_limit_test.go index 85ac262..b5d154d 100644 --- a/backend/edge/shared/interface/middleware/login_rate_limit_test.go +++ b/backend/product/auth/config/login_rate_limit_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" ) func TestResolveLoginRateLimitsUsesSecureDefaults(t *testing.T) { diff --git a/backend/edge/shared/interface/middleware/middleware.go b/backend/product/auth/config/middleware.go similarity index 98% rename from backend/edge/shared/interface/middleware/middleware.go rename to backend/product/auth/config/middleware.go index 86fded4..db181dc 100644 --- a/backend/edge/shared/interface/middleware/middleware.go +++ b/backend/product/auth/config/middleware.go @@ -4,7 +4,7 @@ import ( "os" "strings" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" ) type CORSSettings struct { diff --git a/backend/edge/shared/interface/middleware/token_test.go b/backend/product/auth/config/token_test.go similarity index 100% rename from backend/edge/shared/interface/middleware/token_test.go rename to backend/product/auth/config/token_test.go diff --git a/backend/edge/shared/internal/transport/http/router/authorized/customer_test.go b/backend/product/auth/http/authorized/customer_test.go similarity index 59% rename from backend/edge/shared/internal/transport/http/router/authorized/customer_test.go rename to backend/product/auth/http/authorized/customer_test.go index 26c25bb..979ae33 100644 --- a/backend/edge/shared/internal/transport/http/router/authorized/customer_test.go +++ b/backend/product/auth/http/authorized/customer_test.go @@ -2,18 +2,19 @@ package authorized import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "net/http/httptest" "testing" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/config" + emodel "git.sendico.io/tech/sxchnge/product/auth/model" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/product/customers/store" + "git.sendico.io/tech/sxchnge/shared/errors" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" "github.com/stretchr/testify/require" @@ -23,11 +24,11 @@ import ( type customerDB struct { account.DB - customer model.Account + customer customermodel.Account requested bson.ObjectID } -func (d *customerDB) Get(_ context.Context, id bson.ObjectID, result *model.Account) error { +func (d *customerDB) Get(_ context.Context, id bson.ObjectID, result *customermodel.Account) error { d.requested = id if id != d.customer.ID { return merrors.NoData("customer not found") @@ -35,34 +36,34 @@ func (d *customerDB) Get(_ context.Context, id bson.ObjectID, result *model.Acco *result = d.customer return nil } -func (d *sessionDB) GetClient(context.Context, string) (*model.Client, error) { - return &model.Client{ClientID: "web"}, nil +func (d *sessionDB) GetClient(context.Context, string) (*sessionmodel.Client, error) { + return &sessionmodel.Client{ClientID: "web"}, nil } func TestCustomerHandlerResolvesIdentityWithoutRoles(t *testing.T) { - for _, status := range []model.AccountStatus{model.AccountActive, model.AccountBlocked, model.AccountPendingVerification} { + for _, status := range []customermodel.AccountStatus{customermodel.AccountActive, customermodel.AccountBlocked, customermodel.AccountPendingVerification} { t.Run(string(status), func(t *testing.T) { accounts := &customerDB{} accounts.customer.ID = bson.NewObjectID() accounts.customer.Status = status routes := chi.NewRouter() signature := &middleware.Signature{Algorithm: "HS256", PrivateKey: []byte("test-only-signing-key-at-least-32-bytes")} - router := NewRouter(zap.NewNop(), "/api/v1", routes, accounts, &sessionDB{token: &model.RefreshToken{}}, &middleware.TokenConfig{Expiration: middleware.TokenExpiration{Account: 1}}, signature) + router := NewRouter(zap.NewNop(), "/api/v1", routes, accounts, &sessionDB{token: &sessionmodel.RefreshToken{}}, &middleware.TokenConfig{Expiration: middleware.TokenExpiration{Account: 1}}, signature) called := false - router.AccountHandler(mservice.Accounts, "/me", api.Get, func(_ *http.Request, c *model.Account, _ *sresponse.TokenData) http.HandlerFunc { + router.AccountHandler(mservice.Accounts, "/me", api.Get, func(_ *http.Request, c *customermodel.Account, _ *sresponse.TokenData) http.HandlerFunc { called = true require.Equal(t, accounts.customer.ID, c.ID) return func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) } }) ja := jwtauth.New(signature.Algorithm, signature.PrivateKey, nil) - _, token, err := ja.Encode(emodel.Account2ClaimsForSession(&accounts.customer, 1, model.SessionIdentifier{ClientID: "web", DeviceID: "device", SessionVersion: "v1"})) + _, token, err := ja.Encode(emodel.Account2ClaimsForSession(&accounts.customer, 1, sessionmodel.SessionIdentifier{ClientID: "web", DeviceID: "device", SessionVersion: "v1"})) require.NoError(t, err) req := httptest.NewRequest(http.MethodGet, "/api/v1/accounts/me?accountRef="+bson.NewObjectID().Hex(), nil) req.Header.Set("Authorization", "Bearer "+token) recorder := httptest.NewRecorder() routes.ServeHTTP(recorder, req) require.Equal(t, accounts.customer.ID, accounts.requested) - if status == model.AccountActive { + if status == customermodel.AccountActive { require.Equal(t, http.StatusNoContent, recorder.Code) require.True(t, called) } else { diff --git a/backend/edge/shared/internal/transport/http/router/authorized/handler.go b/backend/product/auth/http/authorized/handler.go similarity index 90% rename from backend/edge/shared/internal/transport/http/router/authorized/handler.go rename to backend/product/auth/http/authorized/handler.go index 0abafd0..d670d26 100644 --- a/backend/edge/shared/internal/transport/http/router/authorized/handler.go +++ b/backend/product/auth/http/authorized/handler.go @@ -2,19 +2,19 @@ package authorized import ( "errors" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" "strings" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/sessionmetrics" - "git.sendico.io/tech/sxchnge/bff/edge/shared/http/ipguard" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/product/auth/ipguard" + emodel "git.sendico.io/tech/sxchnge/product/auth/model" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/product/sessions/metrics" + "git.sendico.io/tech/sxchnge/shared/errors" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging/fields" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/jwtauth/v5" "go.uber.org/zap" ) @@ -120,7 +120,7 @@ func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint strin if t.Pending { return response.Unauthorized(ar.logger, ar.service, "additional verification required") } - var a model.Account + var a customermodel.Account if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil { if errors.Is(err, merrors.ErrNoData) { ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.AccRef(t.AccountRef)) @@ -144,7 +144,7 @@ func (ar *AuthorizedRouter) AccountHandler(service mservice.Type, endpoint strin func (ar *AuthorizedRouter) PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc) { hndlr := func(r *http.Request, t *emodel.AccountToken) http.HandlerFunc { - var a model.Account + var a customermodel.Account if err := ar.db.Get(r.Context(), t.AccountRef, &a); err != nil { if errors.Is(err, merrors.ErrNoData) { ar.logger.Debug("Failed to find related user", zap.Error(err), mzap.AccRef(t.AccountRef)) @@ -152,7 +152,7 @@ func (ar *AuthorizedRouter) PendingAccountHandler(service mservice.Type, endpoin } return response.Internal(ar.logger, ar.service, err) } - if a.Status == model.AccountBlocked || a.IsArchived() { + if a.Status == customermodel.AccountBlocked || a.IsArchived() { return response.Unauthorized(ar.logger, ar.service, "customer is inactive") } return handler(r, &a, t) diff --git a/backend/edge/shared/internal/transport/http/router/authorized/router.go b/backend/product/auth/http/authorized/router.go similarity index 73% rename from backend/edge/shared/internal/transport/http/router/authorized/router.go rename to backend/product/auth/http/authorized/router.go index a7af5b4..17b1495 100644 --- a/backend/edge/shared/internal/transport/http/router/authorized/router.go +++ b/backend/product/auth/http/authorized/router.go @@ -3,17 +3,17 @@ package authorized import ( "net/http" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/sessionmetrics" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model" - re "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router/endpoint" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/product/auth/config" + re "git.sendico.io/tech/sxchnge/product/auth/http/endpoint" + emodel "git.sendico.io/tech/sxchnge/product/auth/model" + "git.sendico.io/tech/sxchnge/product/customers/store" + "git.sendico.io/tech/sxchnge/product/sessions/metrics" + "git.sendico.io/tech/sxchnge/product/sessions/store" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/logging/fields" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" "github.com/go-chi/jwtauth/v5" "go.uber.org/zap" diff --git a/backend/edge/shared/internal/transport/http/router/authorized/session_test.go b/backend/product/auth/http/authorized/session_test.go similarity index 82% rename from backend/edge/shared/internal/transport/http/router/authorized/session_test.go rename to backend/product/auth/http/authorized/session_test.go index 8463636..cb3c715 100644 --- a/backend/edge/shared/internal/transport/http/router/authorized/session_test.go +++ b/backend/product/auth/http/authorized/session_test.go @@ -2,27 +2,27 @@ package authorized import ( "context" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "net/http/httptest" "testing" - emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + emodel "git.sendico.io/tech/sxchnge/product/auth/model" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) type sessionDB struct { refreshtokens.DB - token *model.RefreshToken + token *sessionmodel.RefreshToken err error revokeAllID string } -func (db *sessionDB) GetSession(context.Context, bson.ObjectID, *model.SessionIdentifier) (*model.RefreshToken, error) { +func (db *sessionDB) GetSession(context.Context, bson.ObjectID, *sessionmodel.SessionIdentifier) (*sessionmodel.RefreshToken, error) { return db.token, db.err } @@ -35,7 +35,7 @@ func TestValidateSessionFailsClosed(t *testing.T) { accountRef := bson.NewObjectID() token := &emodel.AccountToken{ AccountRef: accountRef, - SessionIdentifier: model.SessionIdentifier{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: "web", DeviceID: "device-a", SessionVersion: "version-a", @@ -49,7 +49,7 @@ func TestValidateSessionFailsClosed(t *testing.T) { mutate func(*emodel.AccountToken) wantStatus int }{ - {name: "active", db: &sessionDB{token: &model.RefreshToken{}}, wantStatus: 0}, + {name: "active", db: &sessionDB{token: &sessionmodel.RefreshToken{}}, wantStatus: 0}, {name: "revoked", db: &sessionDB{err: merrors.ErrNoData}, wantStatus: http.StatusUnauthorized}, {name: "storage unavailable", db: &sessionDB{err: merrors.Internal("database unavailable")}, wantStatus: http.StatusInternalServerError}, { @@ -93,7 +93,7 @@ func TestLogoutAllRevokesEveryDevice(t *testing.T) { router := &AuthorizedRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} token := &emodel.AccountToken{ AccountRef: bson.NewObjectID(), - SessionIdentifier: model.SessionIdentifier{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: "web", DeviceID: "device-a", SessionVersion: "version-a", diff --git a/backend/edge/shared/internal/transport/http/router/dispatcher.go b/backend/product/auth/http/dispatcher.go similarity index 67% rename from backend/edge/shared/internal/transport/http/router/dispatcher.go rename to backend/product/auth/http/dispatcher.go index 733109b..2a44854 100644 --- a/backend/edge/shared/internal/transport/http/router/dispatcher.go +++ b/backend/product/auth/http/dispatcher.go @@ -5,17 +5,17 @@ import ( "github.com/go-chi/chi/v5" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - rauthorized "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router/authorized" - rpublic "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router/public" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/ratelimits" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/verification" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/config" + rauthorized "git.sendico.io/tech/sxchnge/product/auth/http/authorized" + rpublic "git.sendico.io/tech/sxchnge/product/auth/http/public" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/product/customers/store" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/product/verification/store" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/ratelimit/store" + "git.sendico.io/tech/sxchnge/shared/service" ) type Dispatcher struct { diff --git a/backend/edge/shared/internal/transport/http/router/endpoint/endpoint.go b/backend/product/auth/http/endpoint/endpoint.go similarity index 64% rename from backend/edge/shared/internal/transport/http/router/endpoint/endpoint.go rename to backend/product/auth/http/endpoint/endpoint.go index 5e73352..8f7642d 100644 --- a/backend/edge/shared/internal/transport/http/router/endpoint/endpoint.go +++ b/backend/product/auth/http/endpoint/endpoint.go @@ -2,18 +2,19 @@ package endpoint import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/auth/config" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/go-chi/chi/v5" ) type ( RegistratorT = func(chi.Router, string, http.HandlerFunc) - ResponderFunc = func(ctx context.Context, r *http.Request, session *model.SessionIdentifier, account *model.Account, accessToken *sresponse.TokenData) http.HandlerFunc + ResponderFunc = func(ctx context.Context, r *http.Request, session *sessionmodel.SessionIdentifier, account *customermodel.Account, accessToken *sresponse.TokenData) http.HandlerFunc ) type HttpEndpointRouter struct { diff --git a/backend/edge/shared/internal/transport/http/router/endpoint/install.go b/backend/product/auth/http/endpoint/install.go similarity index 90% rename from backend/edge/shared/internal/transport/http/router/endpoint/install.go rename to backend/product/auth/http/endpoint/install.go index 863e3f5..2c94d88 100644 --- a/backend/edge/shared/internal/transport/http/router/endpoint/install.go +++ b/backend/product/auth/http/endpoint/install.go @@ -4,9 +4,9 @@ import ( "net/http" "path" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/response" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/transport/http/router/endpoint/token.go b/backend/product/auth/http/endpoint/token.go similarity index 63% rename from backend/edge/shared/internal/transport/http/router/endpoint/token.go rename to backend/product/auth/http/endpoint/token.go index 2c6d14b..b6df9e8 100644 --- a/backend/edge/shared/internal/transport/http/router/endpoint/token.go +++ b/backend/product/auth/http/endpoint/token.go @@ -1,15 +1,16 @@ package endpoint import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + emodel "git.sendico.io/tech/sxchnge/product/auth/model" + "git.sendico.io/tech/sxchnge/product/auth/response" "github.com/go-chi/jwtauth/v5" ) -func (er *HttpEndpointRouter) CreateAccessTokenForSession(user *model.Account, session model.SessionIdentifier) (sresponse.TokenData, error) { +func (er *HttpEndpointRouter) CreateAccessTokenForSession(user *customermodel.Account, session sessionmodel.SessionIdentifier) (sresponse.TokenData, error) { ja := jwtauth.New(er.signature.Algorithm, er.signature.PrivateKey, er.signature.PublicKey) _, res, err := ja.Encode(emodel.Account2ClaimsForSession(user, er.config.Expiration.Account, session)) token := sresponse.TokenData{ @@ -19,7 +20,7 @@ func (er *HttpEndpointRouter) CreateAccessTokenForSession(user *model.Account, s return token, err } -func (er *HttpEndpointRouter) CreatePendingToken(user *model.Account, ttlMinutes int) (sresponse.TokenData, error) { +func (er *HttpEndpointRouter) CreatePendingToken(user *customermodel.Account, ttlMinutes int) (sresponse.TokenData, error) { ja := jwtauth.New(er.signature.Algorithm, er.signature.PrivateKey, er.signature.PublicKey) _, res, err := ja.Encode(emodel.PendingAccount2Claims(user, ttlMinutes)) token := sresponse.TokenData{ diff --git a/backend/edge/shared/internal/transport/http/router/public/login.go b/backend/product/auth/http/public/login.go similarity index 84% rename from backend/edge/shared/internal/transport/http/router/public/login.go rename to backend/product/auth/http/public/login.go index f6b3c7f..5807249 100644 --- a/backend/edge/shared/internal/transport/http/router/public/login.go +++ b/backend/product/auth/http/public/login.go @@ -5,19 +5,20 @@ import ( "crypto/subtle" "encoding/json" "errors" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "strings" - clientauth "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/client" - "git.sendico.io/tech/sxchnge/bff/edge/shared/http/ipguard" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mask" + clientauth "git.sendico.io/tech/sxchnge/product/auth/client" + "git.sendico.io/tech/sxchnge/product/auth/ipguard" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/logging/mask" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) @@ -27,12 +28,12 @@ const apiLoginClientAuthMethod = "client_secret_post" const dummyPasswordHash = "$2y$10$pmvNFN5zyooml6Oqh.cGFenBF/indHDE.dyLwjNJW1.Tk2Suvdvve" -func (pr *PublicRouter) authenticateAccount(ctx context.Context, req *srequest.Login) (*model.Account, bool, http.HandlerFunc) { +func (pr *PublicRouter) authenticateAccount(ctx context.Context, req *srequest.Login) (*customermodel.Account, bool, http.HandlerFunc) { // Get the account database entry trimmedLogin := strings.TrimSpace(req.Login) account, err := pr.db.GetByEmail(ctx, strings.ToLower(trimmedLogin)) if errors.Is(err, merrors.ErrNoData) || (err == nil && account == nil) { - (&model.Account{Password: dummyPasswordHash}).MatchPassword(req.Password) + (&customermodel.Account{Password: dummyPasswordHash}).MatchPassword(req.Password) pr.logger.Debug("Login rejected: invalid credentials") return nil, true, response.Unauthorized(pr.logger, pr.service, "invalid credentials") } @@ -53,7 +54,7 @@ func (pr *PublicRouter) authenticateAccount(ctx context.Context, req *srequest.L return account, false, nil } -func (pr *PublicRouter) respondPendingLogin(account *model.Account) http.HandlerFunc { +func (pr *PublicRouter) respondPendingLogin(account *customermodel.Account) http.HandlerFunc { pendingToken, err := pr.imp.CreatePendingToken(account, pendingLoginTTLMinutes) if err != nil { pr.logger.Warn("Failed to generate pending token", zap.Error(err)) @@ -72,7 +73,7 @@ func hasGrantType(grants []string, target string) bool { return false } -func (pr *PublicRouter) validateClientIPPolicy(r *http.Request, clientID string, client *model.Client) http.HandlerFunc { +func (pr *PublicRouter) validateClientIPPolicy(r *http.Request, clientID string, client *sessionmodel.Client) http.HandlerFunc { if client == nil { pr.logger.Debug("Client not found, rejecting authorization", zap.String("client_id", clientID)) return response.Unauthorized(pr.logger, pr.service, clientauth.ErrUnknownClientID.Error()) @@ -94,7 +95,7 @@ func (pr *PublicRouter) validateClientIPPolicy(r *http.Request, clientID string, return nil } -func (pr *PublicRouter) loadClient(ctx context.Context, clientID string) (*model.Client, http.HandlerFunc) { +func (pr *PublicRouter) loadClient(ctx context.Context, clientID string) (*sessionmodel.Client, http.HandlerFunc) { client, err := clientauth.Resolve(ctx, pr.rtdb, clientID) if errors.Is(err, clientauth.ErrUnknownClientID) { pr.logger.Debug("Login rejected: unknown client id", zap.String("client_id", clientID)) @@ -107,7 +108,7 @@ func (pr *PublicRouter) loadClient(ctx context.Context, clientID string) (*model return client, nil } -func (pr *PublicRouter) validateClientAccountBinding(client *model.Client, account *model.Account) http.HandlerFunc { +func (pr *PublicRouter) validateClientAccountBinding(client *sessionmodel.Client, account *customermodel.Account) http.HandlerFunc { if client == nil || client.AccountRef == nil { return nil } @@ -118,7 +119,7 @@ func (pr *PublicRouter) validateClientAccountBinding(client *model.Client, accou return nil } -func (pr *PublicRouter) validateClientState(r *http.Request, clientID string, client *model.Client) http.HandlerFunc { +func (pr *PublicRouter) validateClientState(r *http.Request, clientID string, client *sessionmodel.Client) http.HandlerFunc { if client == nil { return response.Unauthorized(pr.logger, pr.service, clientauth.ErrUnknownClientID.Error()) } @@ -131,7 +132,7 @@ func (pr *PublicRouter) validateClientState(r *http.Request, clientID string, cl return nil } -func (pr *PublicRouter) validateAPIClient(ctx context.Context, r *http.Request, req *srequest.Login, account *model.Account) http.HandlerFunc { +func (pr *PublicRouter) validateAPIClient(ctx context.Context, r *http.Request, req *srequest.Login, account *customermodel.Account) http.HandlerFunc { client, h := pr.loadClient(ctx, req.ClientID) if h != nil { return h @@ -165,7 +166,7 @@ func (pr *PublicRouter) validateAPIClient(ctx context.Context, r *http.Request, return nil } -func (pr *PublicRouter) resolveLoginClient(ctx context.Context, r *http.Request, clientID string) (*model.Client, http.HandlerFunc) { +func (pr *PublicRouter) resolveLoginClient(ctx context.Context, r *http.Request, clientID string) (*sessionmodel.Client, http.HandlerFunc) { clientID = strings.TrimSpace(clientID) if clientID == "" { return nil, nil @@ -180,7 +181,7 @@ func (pr *PublicRouter) resolveLoginClient(ctx context.Context, r *http.Request, return client, nil } -func (pr *PublicRouter) respondAPILogin(ctx context.Context, r *http.Request, req *srequest.Login, account *model.Account) http.HandlerFunc { +func (pr *PublicRouter) respondAPILogin(ctx context.Context, r *http.Request, req *srequest.Login, account *customermodel.Account) http.HandlerFunc { if req.ClientID == "" || req.DeviceID == "" { return response.BadRequest(pr.logger, pr.service, "missing_session", "session identifier is required") } diff --git a/backend/edge/shared/internal/transport/http/router/public/login_rate_limit.go b/backend/product/auth/http/public/login_rate_limit.go similarity index 91% rename from backend/edge/shared/internal/transport/http/router/public/login_rate_limit.go rename to backend/product/auth/http/public/login_rate_limit.go index 483031b..e7520e6 100644 --- a/backend/edge/shared/internal/transport/http/router/public/login_rate_limit.go +++ b/backend/product/auth/http/public/login_rate_limit.go @@ -2,18 +2,18 @@ package public import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" "strconv" "strings" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/http/ipguard" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/ratelimits" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/auth/config" + "git.sendico.io/tech/sxchnge/product/auth/ipguard" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/ratelimit/store" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "go.uber.org/zap" @@ -40,7 +40,7 @@ type loginRateScope struct { func (pr *PublicRouter) authenticateRateLimited( r *http.Request, req *srequest.Login, -) (*model.Account, http.HandlerFunc) { +) (*customermodel.Account, http.HandlerFunc) { if pr.attempts == nil { account, _, h := pr.authenticateAccount(r.Context(), req) return account, h diff --git a/backend/edge/shared/internal/transport/http/router/public/login_rate_limit_test.go b/backend/product/auth/http/public/login_rate_limit_test.go similarity index 93% rename from backend/edge/shared/internal/transport/http/router/public/login_rate_limit_test.go rename to backend/product/auth/http/public/login_rate_limit_test.go index 3412f0c..25785c9 100644 --- a/backend/edge/shared/internal/transport/http/router/public/login_rate_limit_test.go +++ b/backend/product/auth/http/public/login_rate_limit_test.go @@ -2,6 +2,7 @@ package public import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" "net/http/httptest" "strconv" @@ -11,11 +12,10 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/config" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) @@ -86,8 +86,8 @@ func testRateLimitedRouter( func loginRequest(login, password, remoteAddr string) (*http.Request, *srequest.Login) { req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/login", nil) req.RemoteAddr = remoteAddr - return req, &srequest.Login{LoginData: model.LoginData{ - UserDataBase: model.UserDataBase{Login: login}, + return req, &srequest.Login{LoginData: customermodel.LoginData{ + UserDataBase: customermodel.UserDataBase{Login: login}, Password: password, }} } @@ -199,8 +199,8 @@ func TestLoginEndpointReturnsStableRateLimitResponse(t *testing.T) { } func TestInvalidCredentialsDoNotDiscloseAccountExistence(t *testing.T) { - request := &srequest.Login{LoginData: model.LoginData{ - UserDataBase: model.UserDataBase{Login: "user@example.com"}, + request := &srequest.Login{LoginData: customermodel.LoginData{ + UserDataBase: customermodel.UserDataBase{Login: "user@example.com"}, Password: "wrong password", }} @@ -307,10 +307,10 @@ func TestLoginRateLimitFailsClosedWhenFailureCannotBeRecorded(t *testing.T) { } func TestDummyPasswordHashRemainsValid(t *testing.T) { - if !(&model.Account{Password: dummyPasswordHash}).MatchPassword("sendico-login-dummy-password") { + if !(&customermodel.Account{Password: dummyPasswordHash}).MatchPassword("sendico-login-dummy-password") { t.Fatal("dummy password hash is not a valid bcrypt hash") } - if (&model.Account{Password: dummyPasswordHash}).MatchPassword("different") { + if (&customermodel.Account{Password: dummyPasswordHash}).MatchPassword("different") { t.Fatal("dummy password hash unexpectedly matched another password") } } diff --git a/backend/edge/shared/internal/transport/http/router/public/login_test.go b/backend/product/auth/http/public/login_test.go similarity index 88% rename from backend/edge/shared/internal/transport/http/router/public/login_test.go rename to backend/product/auth/http/public/login_test.go index 097a0c5..c73d490 100644 --- a/backend/edge/shared/internal/transport/http/router/public/login_test.go +++ b/backend/product/auth/http/public/login_test.go @@ -3,16 +3,17 @@ package public import ( "context" "errors" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "net/http/httptest" "strings" "testing" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/config" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" "go.uber.org/zap" ) @@ -49,8 +50,8 @@ func TestLoginAuthenticatesAgainstInjectedAccountStorage(t *testing.T) { } got, failed, h := router.authenticateAccount(context.Background(), &srequest.Login{ - LoginData: model.LoginData{ - UserDataBase: model.UserDataBase{ + LoginData: customermodel.LoginData{ + UserDataBase: customermodel.UserDataBase{ Login: "user@example.com", }, Password: "correct horse battery staple", @@ -110,7 +111,7 @@ func testRejectsUnknownClientID( } func TestLoginRejectsPendingVerificationAccount(t *testing.T) { - account := testAccountWithStatus(t, model.AccountPendingVerification) + account := testAccountWithStatus(t, customermodel.AccountPendingVerification) router := &PublicRouter{ logger: zap.NewNop(), db: &testAccountDB{result: account}, @@ -134,7 +135,7 @@ func TestLoginRejectsPendingVerificationAccount(t *testing.T) { } func TestLoginRejectsBlockedAccount(t *testing.T) { - account := testAccountWithStatus(t, model.AccountBlocked) + account := testAccountWithStatus(t, customermodel.AccountBlocked) router := &PublicRouter{ logger: zap.NewNop(), db: &testAccountDB{result: account}, @@ -163,7 +164,7 @@ func TestAPILoginAllowsLoginAgainAfterRevokeSameSession(t *testing.T) { deviceID := "device-1" rtdb := &testRefreshTokenDB{ - client: &model.Client{ + client: &sessionmodel.Client{ ClientID: clientID, ClientSecret: "secret", GrantTypes: []string{"password"}, @@ -214,7 +215,7 @@ func TestAPILoginAllowsLoginAgainAfterRevokeSameSession(t *testing.T) { t.Fatal("expected first login to return refresh token") } - err := rtdb.Revoke(context.Background(), *account.GetID(), &model.SessionIdentifier{ + err := rtdb.Revoke(context.Background(), *account.GetID(), &sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }) @@ -237,8 +238,8 @@ func TestAPILoginAllowsLoginAgainAfterRevokeSameSession(t *testing.T) { t.Fatal("expected second login to issue a different refresh token after revocation") } - _, err = rtdb.GetByCRT(context.Background(), &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + _, err = rtdb.GetByCRT(context.Background(), &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -248,8 +249,8 @@ func TestAPILoginAllowsLoginAgainAfterRevokeSameSession(t *testing.T) { t.Fatalf("expected old refresh token to be unusable, got %v", err) } - _, err = rtdb.GetByCRT(context.Background(), &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + _, err = rtdb.GetByCRT(context.Background(), &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, diff --git a/backend/edge/shared/internal/transport/http/router/public/login_test_support_test.go b/backend/product/auth/http/public/login_test_support_test.go similarity index 79% rename from backend/edge/shared/internal/transport/http/router/public/login_test_support_test.go rename to backend/product/auth/http/public/login_test_support_test.go index df513c2..2a8d6af 100644 --- a/backend/edge/shared/internal/transport/http/router/public/login_test_support_test.go +++ b/backend/product/auth/http/public/login_test_support_test.go @@ -3,12 +3,13 @@ package public import ( "context" "encoding/json" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "sync" "testing" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -49,17 +50,17 @@ func decodeLoginResponse(t *testing.T, body []byte) testLoginResponse { return resp } -func testActiveAccount(t *testing.T) *model.Account { +func testActiveAccount(t *testing.T) *customermodel.Account { t.Helper() - return testAccountWithStatus(t, model.AccountActive) + return testAccountWithStatus(t, customermodel.AccountActive) } -func testAccountWithStatus(t *testing.T, status model.AccountStatus) *model.Account { +func testAccountWithStatus(t *testing.T, status customermodel.AccountStatus) *customermodel.Account { t.Helper() - account := &model.Account{ - AccountPublic: model.AccountPublic{ - UserDataBase: model.UserDataBase{ + account := &customermodel.Account{ + AccountPublic: customermodel.AccountPublic{ + UserDataBase: customermodel.UserDataBase{ Login: "user@example.com", }, }, @@ -75,27 +76,27 @@ func testAccountWithStatus(t *testing.T, status model.AccountStatus) *model.Acco type testAccountDB struct { mu sync.Mutex - result *model.Account + result *customermodel.Account err error getByEmailCalls int } -func (db *testAccountDB) GetByEmail(ctx context.Context, email string) (*model.Account, error) { +func (db *testAccountDB) GetByEmail(ctx context.Context, email string) (*customermodel.Account, error) { db.mu.Lock() defer db.mu.Unlock() db.getByEmailCalls++ return db.result, db.err } -func (db *testAccountDB) Create(ctx context.Context, object *model.Account) error { +func (db *testAccountDB) Create(ctx context.Context, object *customermodel.Account) error { return merrors.NotImplemented("stub") } -func (db *testAccountDB) InsertMany(ctx context.Context, objects []*model.Account) error { +func (db *testAccountDB) InsertMany(ctx context.Context, objects []*customermodel.Account) error { return merrors.NotImplemented("stub") } -func (db *testAccountDB) Get(ctx context.Context, objectRef bson.ObjectID, result *model.Account) error { +func (db *testAccountDB) Get(ctx context.Context, objectRef bson.ObjectID, result *customermodel.Account) error { if db.err != nil { return db.err } @@ -106,7 +107,7 @@ func (db *testAccountDB) Get(ctx context.Context, objectRef bson.ObjectID, resul return nil } -func (db *testAccountDB) Update(ctx context.Context, object *model.Account) error { +func (db *testAccountDB) Update(ctx context.Context, object *customermodel.Account) error { return merrors.NotImplemented("stub") } @@ -126,21 +127,21 @@ func (db *testAccountDB) DeleteCascade(ctx context.Context, objectRef bson.Objec return merrors.NotImplemented("stub") } -func (db *testAccountDB) FindOne(ctx context.Context, query builder.Query, result *model.Account) error { +func (db *testAccountDB) FindOne(ctx context.Context, query builder.Query, result *customermodel.Account) error { return merrors.NotImplemented("stub") } type testRefreshTokenDB struct { - client *model.Client + client *sessionmodel.Client clientErr error - tokens []*model.RefreshToken + tokens []*sessionmodel.RefreshToken } -func (db *testRefreshTokenDB) GetClient(ctx context.Context, clientID string) (*model.Client, error) { +func (db *testRefreshTokenDB) GetClient(ctx context.Context, clientID string) (*sessionmodel.Client, error) { return db.client, db.clientErr } -func (db *testRefreshTokenDB) Revoke(ctx context.Context, accountRef bson.ObjectID, session *model.SessionIdentifier) error { +func (db *testRefreshTokenDB) Revoke(ctx context.Context, accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) error { for _, token := range db.tokens { if token.AccountRef == nil || *token.AccountRef != accountRef { continue @@ -153,7 +154,7 @@ func (db *testRefreshTokenDB) Revoke(ctx context.Context, accountRef bson.Object return nil } -func (db *testRefreshTokenDB) RevokeToken(ctx context.Context, token *model.ClientRefreshToken) (bool, error) { +func (db *testRefreshTokenDB) RevokeToken(ctx context.Context, token *sessionmodel.ClientRefreshToken) (bool, error) { for _, stored := range db.tokens { if stored.ClientID == token.ClientID && stored.DeviceID == token.DeviceID && stored.RefreshToken == token.RefreshToken && !stored.IsRevoked { stored.IsRevoked = true @@ -167,7 +168,7 @@ func (db *testRefreshTokenDB) RevokeAll(ctx context.Context, accountRef bson.Obj return merrors.NotImplemented("stub") } -func (db *testRefreshTokenDB) GetSession(ctx context.Context, accountRef bson.ObjectID, session *model.SessionIdentifier) (*model.RefreshToken, error) { +func (db *testRefreshTokenDB) GetSession(ctx context.Context, accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) (*sessionmodel.RefreshToken, error) { for _, token := range db.tokens { if token.AccountRef != nil && *token.AccountRef == accountRef && token.ClientID == session.ClientID && token.DeviceID == session.DeviceID && !token.IsRevoked { return cloneRefreshToken(token), nil @@ -176,7 +177,7 @@ func (db *testRefreshTokenDB) GetSession(ctx context.Context, accountRef bson.Ob return nil, merrors.ErrNoData } -func (db *testRefreshTokenDB) GetByCRT(ctx context.Context, t *model.ClientRefreshToken) (*model.RefreshToken, error) { +func (db *testRefreshTokenDB) GetByCRT(ctx context.Context, t *sessionmodel.ClientRefreshToken) (*sessionmodel.RefreshToken, error) { for _, token := range db.tokens { if token.ClientID == t.ClientID && token.DeviceID == t.DeviceID && token.RefreshToken == t.RefreshToken { if token.IsRevoked { @@ -188,7 +189,7 @@ func (db *testRefreshTokenDB) GetByCRT(ctx context.Context, t *model.ClientRefre return nil, merrors.ErrNoData } -func (db *testRefreshTokenDB) Create(ctx context.Context, object *model.RefreshToken) error { +func (db *testRefreshTokenDB) Create(ctx context.Context, object *sessionmodel.RefreshToken) error { for _, token := range db.tokens { if token.AccountRef == nil || object.AccountRef == nil { continue @@ -218,15 +219,15 @@ func (db *testRefreshTokenDB) Create(ctx context.Context, object *model.RefreshT return nil } -func (db *testRefreshTokenDB) InsertMany(ctx context.Context, objects []*model.RefreshToken) error { +func (db *testRefreshTokenDB) InsertMany(ctx context.Context, objects []*sessionmodel.RefreshToken) error { return merrors.NotImplemented("stub") } -func (db *testRefreshTokenDB) Get(ctx context.Context, objectRef bson.ObjectID, result *model.RefreshToken) error { +func (db *testRefreshTokenDB) Get(ctx context.Context, objectRef bson.ObjectID, result *sessionmodel.RefreshToken) error { return merrors.NotImplemented("stub") } -func (db *testRefreshTokenDB) Update(ctx context.Context, object *model.RefreshToken) error { +func (db *testRefreshTokenDB) Update(ctx context.Context, object *sessionmodel.RefreshToken) error { return merrors.NotImplemented("stub") } @@ -246,7 +247,7 @@ func (db *testRefreshTokenDB) DeleteCascade(ctx context.Context, objectRef bson. return merrors.NotImplemented("stub") } -func (db *testRefreshTokenDB) FindOne(ctx context.Context, query builder.Query, result *model.RefreshToken) error { +func (db *testRefreshTokenDB) FindOne(ctx context.Context, query builder.Query, result *sessionmodel.RefreshToken) error { return merrors.NotImplemented("stub") } @@ -270,7 +271,7 @@ func (db *testRefreshTokenDB) activeTokenCountForSession(accountRef bson.ObjectI return count } -func cloneRefreshToken(token *model.RefreshToken) *model.RefreshToken { +func cloneRefreshToken(token *sessionmodel.RefreshToken) *sessionmodel.RefreshToken { if token == nil { return nil } diff --git a/backend/edge/shared/internal/transport/http/router/public/logout.go b/backend/product/auth/http/public/logout.go similarity index 87% rename from backend/edge/shared/internal/transport/http/router/public/logout.go rename to backend/product/auth/http/public/logout.go index 99ad108..c4fa2e1 100644 --- a/backend/edge/shared/internal/transport/http/router/public/logout.go +++ b/backend/product/auth/http/public/logout.go @@ -4,11 +4,11 @@ import ( "encoding/json" "net/http" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/sessionmetrics" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/product/sessions/browser" + "git.sendico.io/tech/sxchnge/product/sessions/metrics" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/transport/http/router/public/logout_test.go b/backend/product/auth/http/public/logout_test.go similarity index 85% rename from backend/edge/shared/internal/transport/http/router/public/logout_test.go rename to backend/product/auth/http/public/logout_test.go index 9b162f0..36a3bc8 100644 --- a/backend/edge/shared/internal/transport/http/router/public/logout_test.go +++ b/backend/product/auth/http/public/logout_test.go @@ -2,6 +2,7 @@ package public import ( "context" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "net/http/httptest" "strings" @@ -9,11 +10,10 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/sessions/browser" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" @@ -22,12 +22,12 @@ import ( type logoutDB struct { refreshtokens.DB mu sync.Mutex - tokens map[string]*model.RefreshToken + tokens map[string]*sessionmodel.RefreshToken lookupErr error revokeErr error } -func (db *logoutDB) RevokeToken(_ context.Context, request *model.ClientRefreshToken) (bool, error) { +func (db *logoutDB) RevokeToken(_ context.Context, request *sessionmodel.ClientRefreshToken) (bool, error) { db.mu.Lock() defer db.mu.Unlock() if db.lookupErr != nil { @@ -44,10 +44,10 @@ func (db *logoutDB) RevokeToken(_ context.Context, request *model.ClientRefreshT return true, nil } -func newLogoutToken(accountRef bson.ObjectID, deviceID, value string) *model.RefreshToken { - return &model.RefreshToken{ - ClientRefreshToken: model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ClientID: "web", DeviceID: deviceID, SessionVersion: "version-" + deviceID}, +func newLogoutToken(accountRef bson.ObjectID, deviceID, value string) *sessionmodel.RefreshToken { + return &sessionmodel.RefreshToken{ + ClientRefreshToken: sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ClientID: "web", DeviceID: deviceID, SessionVersion: "version-" + deviceID}, RefreshToken: value, }, AccountRef: &accountRef, @@ -65,7 +65,7 @@ func executeLogout(router *PublicRouter, deviceID, refreshToken string) int { func TestLogoutRevokesOnlyCurrentSessionAndIsIdempotent(t *testing.T) { accountRef := bson.NewObjectID() - db := &logoutDB{tokens: map[string]*model.RefreshToken{ + db := &logoutDB{tokens: map[string]*sessionmodel.RefreshToken{ "device-a": newLogoutToken(accountRef, "device-a", "refresh-a"), "device-b": newLogoutToken(accountRef, "device-b", "refresh-b"), }} @@ -87,7 +87,7 @@ func TestLogoutRevokesOnlyCurrentSessionAndIsIdempotent(t *testing.T) { func TestConcurrentLogoutIsIdempotent(t *testing.T) { accountRef := bson.NewObjectID() - db := &logoutDB{tokens: map[string]*model.RefreshToken{ + db := &logoutDB{tokens: map[string]*sessionmodel.RefreshToken{ "device-a": newLogoutToken(accountRef, "device-a", "refresh-a"), }} router := &PublicRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} @@ -112,7 +112,7 @@ func TestConcurrentLogoutIsIdempotent(t *testing.T) { func TestDelayedLogoutDoesNotRevokeReplacementSession(t *testing.T) { accountRef := bson.NewObjectID() - db := &logoutDB{tokens: map[string]*model.RefreshToken{ + db := &logoutDB{tokens: map[string]*sessionmodel.RefreshToken{ "device-a": newLogoutToken(accountRef, "device-a", "new-refresh"), }} router := &PublicRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} @@ -129,7 +129,7 @@ func TestLogoutAcceptsAnAlreadyExpiredSession(t *testing.T) { accountRef := bson.NewObjectID() expired := newLogoutToken(accountRef, "device-a", "expired-refresh") expired.ExpiresAt = time.Now().Add(-time.Hour) - db := &logoutDB{tokens: map[string]*model.RefreshToken{"device-a": expired}} + db := &logoutDB{tokens: map[string]*sessionmodel.RefreshToken{"device-a": expired}} router := &PublicRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} if status := executeLogout(router, "device-a", "expired-refresh"); status != http.StatusOK { @@ -151,7 +151,7 @@ func TestLogoutFailsClosedWhenSessionStoreIsUnavailable(t *testing.T) { func TestBrowserLogoutUsesCookieCredentialAndClearsIt(t *testing.T) { accountRef := bson.NewObjectID() token := newLogoutToken(accountRef, "device-a", "cookie-refresh") - db := &logoutDB{tokens: map[string]*model.RefreshToken{"device-a": token}} + db := &logoutDB{tokens: map[string]*sessionmodel.RefreshToken{"device-a": token}} router := &PublicRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} body := `{"clientId":"web","deviceId":"device-a","token":"body-must-be-ignored"}` request := httptest.NewRequest(http.MethodPost, "https://app.sendico.io/api/v1/accounts/logout", strings.NewReader(body)) @@ -171,7 +171,7 @@ func TestBrowserLogoutUsesCookieCredentialAndClearsIt(t *testing.T) { } func TestBrowserLogoutWithoutCookieIsIdempotentAndClearsBrowserState(t *testing.T) { - db := &logoutDB{tokens: map[string]*model.RefreshToken{}} + db := &logoutDB{tokens: map[string]*sessionmodel.RefreshToken{}} router := &PublicRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} body := `{"clientId":"web","deviceId":"device-a","token":"body-must-be-ignored"}` request := httptest.NewRequest(http.MethodPost, "https://app.sendico.io/api/v1/accounts/logout", strings.NewReader(body)) @@ -187,7 +187,7 @@ func TestBrowserLogoutWithoutCookieIsIdempotentAndClearsBrowserState(t *testing. func TestBrowserLogoutKeepsCookieWhenRevocationFails(t *testing.T) { db := &logoutDB{ - tokens: map[string]*model.RefreshToken{}, + tokens: map[string]*sessionmodel.RefreshToken{}, lookupErr: merrors.Internal("database unavailable"), } router := &PublicRouter{logger: zap.NewNop(), rtdb: db, service: mservice.Accounts} diff --git a/backend/edge/shared/internal/transport/http/router/public/refresh.go b/backend/product/auth/http/public/refresh.go similarity index 84% rename from backend/edge/shared/internal/transport/http/router/public/refresh.go rename to backend/product/auth/http/public/refresh.go index fcacefe..53f31a0 100644 --- a/backend/edge/shared/internal/transport/http/router/public/refresh.go +++ b/backend/product/auth/http/public/refresh.go @@ -6,11 +6,11 @@ import ( "net/http" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/product/sessions/browser" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/transport/http/router/public/refresh_test.go b/backend/product/auth/http/public/refresh_test.go similarity index 89% rename from backend/edge/shared/internal/transport/http/router/public/refresh_test.go rename to backend/product/auth/http/public/refresh_test.go index 2a8dabf..7a8810a 100644 --- a/backend/edge/shared/internal/transport/http/router/public/refresh_test.go +++ b/backend/product/auth/http/public/refresh_test.go @@ -2,15 +2,16 @@ package public import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "net/http/httptest" "strings" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/auth/config" + "git.sendico.io/tech/sxchnge/product/sessions/browser" "github.com/go-chi/chi/v5" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -22,11 +23,11 @@ func newBrowserRefreshRouter(t *testing.T, expiresAt time.Time) (*PublicRouter, clientID := "web" deviceID := "browser-1" rtdb := &testRefreshTokenDB{ - client: &model.Client{ClientID: clientID}, - tokens: []*model.RefreshToken{ + client: &sessionmodel.Client{ClientID: clientID}, + tokens: []*sessionmodel.RefreshToken{ { - ClientRefreshToken: model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + ClientRefreshToken: sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, SessionVersion: "version-1", @@ -137,7 +138,7 @@ func TestNativeRefreshRejectsStaleTokenWithoutCookieMutation(t *testing.T) { } func TestRefreshRejectsInactiveCustomerAndClientMismatch(t *testing.T) { - for _, status := range []model.AccountStatus{model.AccountBlocked, model.AccountPendingVerification} { + for _, status := range []customermodel.AccountStatus{customermodel.AccountBlocked, customermodel.AccountPendingVerification} { t.Run(string(status), func(t *testing.T) { router, _ := newBrowserRefreshRouter(t, time.Now().Add(24*time.Hour)) router.db.(*testAccountDB).result.Status = status diff --git a/backend/edge/shared/internal/transport/http/router/public/respond.go b/backend/product/auth/http/public/respond.go similarity index 72% rename from backend/edge/shared/internal/transport/http/router/public/respond.go rename to backend/product/auth/http/public/respond.go index 720212b..17a669a 100644 --- a/backend/edge/shared/internal/transport/http/router/public/respond.go +++ b/backend/product/auth/http/public/respond.go @@ -2,21 +2,22 @@ package public import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" - rtokens "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/tokens" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/product/auth/response" + rtokens "git.sendico.io/tech/sxchnge/product/sessions/tokens" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.uber.org/zap" ) func (pr *PublicRouter) refreshAndRespondLogin( ctx context.Context, r *http.Request, - session *model.SessionIdentifier, - account *model.Account, + session *sessionmodel.SessionIdentifier, + account *customermodel.Account, ) http.HandlerFunc { refreshToken, err := rtokens.PrepareRefreshToken( ctx, diff --git a/backend/edge/shared/internal/transport/http/router/public/rotate.go b/backend/product/auth/http/public/rotate.go similarity index 86% rename from backend/edge/shared/internal/transport/http/router/public/rotate.go rename to backend/product/auth/http/public/rotate.go index e7b5883..a72cbb9 100644 --- a/backend/edge/shared/internal/transport/http/router/public/rotate.go +++ b/backend/product/auth/http/public/rotate.go @@ -5,9 +5,9 @@ import ( "errors" "net/http" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) diff --git a/backend/edge/shared/internal/transport/http/router/public/router.go b/backend/product/auth/http/public/router.go similarity index 71% rename from backend/edge/shared/internal/transport/http/router/public/router.go rename to backend/product/auth/http/public/router.go index d6b537f..4c8e084 100644 --- a/backend/edge/shared/internal/transport/http/router/public/router.go +++ b/backend/product/auth/http/public/router.go @@ -3,16 +3,16 @@ package public import ( "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - re "git.sendico.io/tech/sxchnge/bff/edge/shared/internal/transport/http/router/endpoint" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/ratelimits" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/verification" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/config" + re "git.sendico.io/tech/sxchnge/product/auth/http/endpoint" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/product/customers/store" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/product/verification/store" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/ratelimit/store" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/go-chi/chi/v5" ) diff --git a/backend/edge/shared/internal/transport/http/router/public/validate.go b/backend/product/auth/http/public/validate.go similarity index 83% rename from backend/edge/shared/internal/transport/http/router/public/validate.go rename to backend/product/auth/http/public/validate.go index 0d4b93e..b8fab90 100644 --- a/backend/edge/shared/internal/transport/http/router/public/validate.go +++ b/backend/product/auth/http/public/validate.go @@ -3,15 +3,16 @@ package public import ( "context" "errors" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/srequest" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/product/auth/request" + "git.sendico.io/tech/sxchnge/product/auth/response" + "git.sendico.io/tech/sxchnge/product/sessions/browser" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.uber.org/zap" ) @@ -21,7 +22,7 @@ func isExpectedRefreshValidationError(err error) bool { return errors.Is(err, merrors.ErrUnauthorized) } -func validateToken(token string, rt *model.RefreshToken) string { +func validateToken(token string, rt *sessionmodel.RefreshToken) string { if rt.AccountRef == nil { return "missing account reference" } @@ -38,7 +39,7 @@ func validateToken(token string, rt *model.RefreshToken) string { return "" } -func (pr *PublicRouter) validateRefreshToken(ctx context.Context, r *http.Request, req *srequest.TokenRefreshRotate) (*model.Account, *sresponse.TokenData, *model.RefreshToken, error) { +func (pr *PublicRouter) validateRefreshToken(ctx context.Context, r *http.Request, req *srequest.TokenRefreshRotate) (*customermodel.Account, *sresponse.TokenData, *sessionmodel.RefreshToken, error) { credential, err := browser.RefreshCredential(r, req.RefreshToken) if err != nil { return nil, nil, nil, err @@ -79,7 +80,7 @@ func (pr *PublicRouter) validateRefreshToken(ctx context.Context, r *http.Reques return nil, nil, nil, merrors.Unauthorized(reason) } - var account model.Account + var account customermodel.Account if err := pr.db.Get(ctx, *rt.AccountRef, &account); errors.Is(err, merrors.ErrNoData) { pr.logger.Debug("User not found while rotating refresh token", zap.Error(err), mzap.AccRef(*rt.AccountRef)) return nil, nil, nil, merrors.Unauthorized("user not found") diff --git a/backend/edge/shared/internal/transport/http/router/router.go b/backend/product/auth/http/router.go similarity index 72% rename from backend/edge/shared/internal/transport/http/router/router.go rename to backend/product/auth/http/router.go index eff32e1..2580c5b 100644 --- a/backend/edge/shared/internal/transport/http/router/router.go +++ b/backend/product/auth/http/router.go @@ -1,9 +1,9 @@ package router import ( - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/product/auth/response" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/service" ) type APIRouter interface { diff --git a/backend/edge/shared/http/ipguard/ipguard.go b/backend/product/auth/ipguard/ipguard.go similarity index 100% rename from backend/edge/shared/http/ipguard/ipguard.go rename to backend/product/auth/ipguard/ipguard.go diff --git a/backend/edge/shared/http/ipguard/ipguard_test.go b/backend/product/auth/ipguard/ipguard_test.go similarity index 100% rename from backend/edge/shared/http/ipguard/ipguard_test.go rename to backend/product/auth/ipguard/ipguard_test.go diff --git a/backend/edge/shared/interface/model/token.go b/backend/product/auth/model/token.go similarity index 82% rename from backend/edge/shared/interface/model/token.go rename to backend/product/auth/model/token.go index ac94199..5988758 100644 --- a/backend/edge/shared/interface/model/token.go +++ b/backend/product/auth/model/token.go @@ -2,12 +2,13 @@ package model import ( "fmt" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - mduration "git.sendico.io/tech/sxchnge/bff/pkg/mutil/duration" + "git.sendico.io/tech/sxchnge/product/auth/config" + mduration "git.sendico.io/tech/sxchnge/shared/duration" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -16,12 +17,12 @@ type AccountToken struct { Login string Name string Locale string - model.SessionIdentifier + sessionmodel.SessionIdentifier Expiration time.Time Pending bool } -func createAccountToken(a *model.Account, expiration int, session model.SessionIdentifier) AccountToken { +func createAccountToken(a *customermodel.Account, expiration int, session sessionmodel.SessionIdentifier) AccountToken { return AccountToken{ AccountRef: *a.GetID(), Login: a.Login, @@ -110,7 +111,7 @@ func Claims2Token(claims middleware.MapClaims) (*AccountToken, error) { return &at, nil } -func Account2ClaimsForSession(a *model.Account, expiration int, session model.SessionIdentifier) middleware.MapClaims { +func Account2ClaimsForSession(a *customermodel.Account, expiration int, session sessionmodel.SessionIdentifier) middleware.MapClaims { t := createAccountToken(a, expiration, session) return middleware.MapClaims{ paramNameID: t.AccountRef.Hex(), @@ -125,8 +126,8 @@ func Account2ClaimsForSession(a *model.Account, expiration int, session model.Se } } -func PendingAccount2Claims(a *model.Account, expirationMinutes int) middleware.MapClaims { - t := createAccountToken(a, expirationMinutes/60, model.SessionIdentifier{}) +func PendingAccount2Claims(a *customermodel.Account, expirationMinutes int) middleware.MapClaims { + t := createAccountToken(a, expirationMinutes/60, sessionmodel.SessionIdentifier{}) t.Expiration = time.Now().Add(time.Duration(expirationMinutes) * time.Minute) t.Pending = true return middleware.MapClaims{ diff --git a/backend/edge/shared/interface/model/token_test.go b/backend/product/auth/model/token_test.go similarity index 76% rename from backend/edge/shared/interface/model/token_test.go rename to backend/product/auth/model/token_test.go index 741ba90..1c24e9d 100644 --- a/backend/edge/shared/interface/model/token_test.go +++ b/backend/product/auth/model/token_test.go @@ -1,19 +1,19 @@ package model import ( - "testing" - - basemodel "git.sendico.io/tech/sxchnge/bff/pkg/model" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "go.mongodb.org/mongo-driver/v2/bson" + "testing" ) func TestAccountTokenPreservesSessionIdentity(t *testing.T) { - account := &basemodel.Account{} + account := &customermodel.Account{} account.SetID(bson.NewObjectID()) account.Login = "security@example.com" account.Name = "Security" account.Locale = "en" - session := basemodel.SessionIdentifier{ + session := sessionmodel.SessionIdentifier{ ClientID: "web", DeviceID: "device-a", SessionVersion: "session-version-a", diff --git a/backend/product/auth/request/login.go b/backend/product/auth/request/login.go new file mode 100644 index 0000000..cc5feb0 --- /dev/null +++ b/backend/product/auth/request/login.go @@ -0,0 +1,11 @@ +package srequest + +import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" + +import customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + +type Login struct { + sessionmodel.SessionIdentifier `json:",inline"` + customermodel.LoginData `json:"login"` + ClientSecret string `json:"clientSecret,omitempty"` +} diff --git a/backend/product/auth/request/refresh.go b/backend/product/auth/request/refresh.go new file mode 100644 index 0000000..7de953e --- /dev/null +++ b/backend/product/auth/request/refresh.go @@ -0,0 +1,5 @@ +package srequest + +import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" + +type AccessTokenRefresh = sessionmodel.ClientRefreshToken diff --git a/backend/product/auth/request/rotate.go b/backend/product/auth/request/rotate.go new file mode 100644 index 0000000..ac440b0 --- /dev/null +++ b/backend/product/auth/request/rotate.go @@ -0,0 +1,5 @@ +package srequest + +import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" + +type TokenRefreshRotate = sessionmodel.ClientRefreshToken diff --git a/backend/edge/shared/interface/api/srequest/validateable.go b/backend/product/auth/request/validateable.go similarity index 100% rename from backend/edge/shared/interface/api/srequest/validateable.go rename to backend/product/auth/request/validateable.go diff --git a/backend/edge/shared/interface/api/srequest/verification.go b/backend/product/auth/request/verification.go similarity index 55% rename from backend/edge/shared/interface/api/srequest/verification.go rename to backend/product/auth/request/verification.go index 551e5ca..1dda9a2 100644 --- a/backend/edge/shared/interface/api/srequest/verification.go +++ b/backend/product/auth/request/verification.go @@ -1,6 +1,6 @@ package srequest -import "git.sendico.io/tech/sxchnge/bff/pkg/model" +import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" type VerificationCodeRequest struct { Purpose string `json:"purpose"` @@ -10,6 +10,6 @@ type VerificationCodeRequest struct { type CodeVerificationRequest struct { VerificationCodeRequest `json:",inline"` - Code string `json:"code"` - SessionIdentifier model.SessionIdentifier `json:"sessionIdentifier"` + Code string `json:"code"` + SessionIdentifier sessionmodel.SessionIdentifier `json:"sessionIdentifier"` } diff --git a/backend/edge/shared/interface/api/sresponse/account.go b/backend/product/auth/response/account.go similarity index 68% rename from backend/edge/shared/interface/api/sresponse/account.go rename to backend/product/auth/response/account.go index ee2c821..a292273 100644 --- a/backend/edge/shared/interface/api/sresponse/account.go +++ b/backend/product/auth/response/account.go @@ -1,11 +1,11 @@ package sresponse import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" ) type AccountData struct { @@ -22,14 +22,14 @@ type accountResponse struct { Account AccountData `json:"account"` } -func NewAccountData(account *model.Account) *AccountData { +func NewAccountData(account *customermodel.Account) *AccountData { return &AccountData{ ID: account.ID.Hex(), Name: account.Name, LastName: account.LastName, Login: account.Login, Locale: account.Locale, AvatarURL: account.AvatarURL, } } -func Account(logger mlogger.Logger, account *model.Account, accessToken *TokenData) http.HandlerFunc { +func Account(logger mlogger.Logger, account *customermodel.Account, accessToken *TokenData) http.HandlerFunc { return response.Ok( logger, &accountResponse{ diff --git a/backend/edge/shared/interface/api/sresponse/authresp.go b/backend/product/auth/response/authresp.go similarity index 100% rename from backend/edge/shared/interface/api/sresponse/authresp.go rename to backend/product/auth/response/authresp.go diff --git a/backend/edge/shared/interface/api/sresponse/file.go b/backend/product/auth/response/file.go similarity index 68% rename from backend/edge/shared/interface/api/sresponse/file.go rename to backend/product/auth/response/file.go index bd07d78..ab198ce 100644 --- a/backend/edge/shared/interface/api/sresponse/file.go +++ b/backend/product/auth/response/file.go @@ -3,8 +3,8 @@ package sresponse import ( "net/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" ) type fileUploaded struct { diff --git a/backend/edge/shared/interface/api/sresponse/login.go b/backend/product/auth/response/login.go similarity index 65% rename from backend/edge/shared/interface/api/sresponse/login.go rename to backend/product/auth/response/login.go index c987f10..e26d9c3 100644 --- a/backend/edge/shared/interface/api/sresponse/login.go +++ b/backend/product/auth/response/login.go @@ -1,12 +1,12 @@ package sresponse import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/sessions/browser" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" ) type loginResponse struct { @@ -14,7 +14,7 @@ type loginResponse struct { RefreshToken TokenData `json:"refreshToken"` } -func Login(logger mlogger.Logger, account *model.Account, accessToken, refreshToken *TokenData) http.HandlerFunc { +func Login(logger mlogger.Logger, account *customermodel.Account, accessToken, refreshToken *TokenData) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { responseRefreshToken := *refreshToken if browser.UsesCookie(r) { diff --git a/backend/edge/shared/interface/api/sresponse/login_pending.go b/backend/product/auth/response/login_pending.go similarity index 61% rename from backend/edge/shared/interface/api/sresponse/login_pending.go rename to backend/product/auth/response/login_pending.go index d0333d0..ab676a9 100644 --- a/backend/edge/shared/interface/api/sresponse/login_pending.go +++ b/backend/product/auth/response/login_pending.go @@ -1,11 +1,11 @@ package sresponse import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" ) type pendingLoginResponse struct { @@ -14,7 +14,7 @@ type pendingLoginResponse struct { Target string `json:"target"` } -func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, target string) http.HandlerFunc { +func LoginPending(logger mlogger.Logger, account *customermodel.Account, pendingToken *TokenData, target string) http.HandlerFunc { return response.Accepted( logger, &pendingLoginResponse{ diff --git a/backend/edge/shared/interface/api/sresponse/login_test.go b/backend/product/auth/response/login_test.go similarity index 84% rename from backend/edge/shared/interface/api/sresponse/login_test.go rename to backend/product/auth/response/login_test.go index 9866324..4637341 100644 --- a/backend/edge/shared/interface/api/sresponse/login_test.go +++ b/backend/product/auth/response/login_test.go @@ -1,14 +1,14 @@ package sresponse import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "net/http" "net/http/httptest" "strings" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/sessions/browser" "github.com/stretchr/testify/require" "go.uber.org/zap" ) @@ -21,7 +21,7 @@ func TestBrowserLoginMovesRefreshCredentialToHttpOnlyCookie(t *testing.T) { req.Header.Set("X-Forwarded-Proto", "https") recorder := httptest.NewRecorder() - Login(zap.NewNop(), &model.Account{}, access, refresh).ServeHTTP(recorder, req) + Login(zap.NewNop(), &customermodel.Account{}, access, refresh).ServeHTTP(recorder, req) require.Equal(t, http.StatusOK, recorder.Code) require.NotContains(t, recorder.Body.String(), refresh.Token) @@ -39,14 +39,14 @@ func TestNativeLoginKeepsRefreshCredentialInResponse(t *testing.T) { req := httptest.NewRequest(http.MethodPost, "http://native.test/api/v1/accounts/login", nil) recorder := httptest.NewRecorder() - Login(zap.NewNop(), &model.Account{}, access, refresh).ServeHTTP(recorder, req) + Login(zap.NewNop(), &customermodel.Account{}, access, refresh).ServeHTTP(recorder, req) require.Contains(t, recorder.Body.String(), refresh.Token) require.Empty(t, recorder.Header().Get("Set-Cookie")) } func TestCustomerResponseExcludesStorageAndPlatformFields(t *testing.T) { - account := &model.Account{Password: "private-password-hash", Status: model.AccountActive} + account := &customermodel.Account{Password: "private-password-hash", Status: customermodel.AccountActive} account.Login = "customer@example.test" account.Name = "Customer" recorder := httptest.NewRecorder() diff --git a/backend/product/auth/response/response.go b/backend/product/auth/response/response.go new file mode 100644 index 0000000..502f575 --- /dev/null +++ b/backend/product/auth/response/response.go @@ -0,0 +1,14 @@ +package sresponse + +import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + "net/http" + + emodel "git.sendico.io/tech/sxchnge/product/auth/model" +) + +type ( + HandlerFunc = func(r *http.Request) http.HandlerFunc + AccountHandlerFunc = func(r *http.Request, account *customermodel.Account, accessToken *TokenData) http.HandlerFunc + PendingAccountHandlerFunc = func(r *http.Request, account *customermodel.Account, token *emodel.AccountToken) http.HandlerFunc +) diff --git a/backend/edge/shared/interface/api/sresponse/result.go b/backend/product/auth/response/result.go similarity index 85% rename from backend/edge/shared/interface/api/sresponse/result.go rename to backend/product/auth/response/result.go index 3ce4b12..48b5ba1 100644 --- a/backend/edge/shared/interface/api/sresponse/result.go +++ b/backend/product/auth/response/result.go @@ -3,8 +3,8 @@ package sresponse import ( "net/http" - "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/http/response" + "git.sendico.io/tech/sxchnge/shared/logging" ) type resultAuth struct { diff --git a/backend/edge/shared/interface/api/sresponse/token.go b/backend/product/auth/response/token.go similarity index 100% rename from backend/edge/shared/interface/api/sresponse/token.go rename to backend/product/auth/response/token.go diff --git a/backend/edge/shared/interface/api/sresponse/verification.go b/backend/product/auth/response/verification.go similarity index 100% rename from backend/edge/shared/interface/api/sresponse/verification.go rename to backend/product/auth/response/verification.go diff --git a/backend/edge/shared/auth/storage/stores.go b/backend/product/auth/storage/stores.go similarity index 84% rename from backend/edge/shared/auth/storage/stores.go rename to backend/product/auth/storage/stores.go index 30edf34..1e84b5f 100644 --- a/backend/edge/shared/auth/storage/stores.go +++ b/backend/product/auth/storage/stores.go @@ -1,14 +1,15 @@ package storage import ( - edgeapi "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - edgemongo "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/mongo" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/ratelimits" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/verification" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/product/auth/config" + "git.sendico.io/tech/sxchnge/product/customers/store" + "git.sendico.io/tech/sxchnge/product/customers/store/mongo" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/product/verification/store" + "git.sendico.io/tech/sxchnge/shared/database" + edgemongo "git.sendico.io/tech/sxchnge/shared/database/dependencies" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/ratelimit/store" "go.uber.org/zap" ) @@ -26,13 +27,13 @@ type TokenStores struct { type resolvedConfig struct { deps *edgemongo.Dependencies - storage edgeapi.AuthStorageConfig + storage middleware.AuthStorageConfig } func OpenStores( logger mlogger.Logger, dbf db.Factory, - config *edgeapi.AuthStorageConfig, + config *middleware.AuthStorageConfig, ) (*Stores, error) { resolved, err := resolve(logger, dbf, config) if err != nil { @@ -48,7 +49,7 @@ func OpenStores( zap.String("verification_rate_limits_collection", resolved.storage.VerificationRateLimitsCollection), ) - adb, err := dbf.NewAccountDBWithCollection(resolved.storage.AccountsCollection) + adb, err := accountdb.CreateWithCollection(logger, resolved.deps.Database, resolved.storage.AccountsCollection) if err != nil { logger.Error("Failed to create account database", zap.Error(err), zap.String("collection", resolved.storage.AccountsCollection)) return nil, err @@ -76,7 +77,7 @@ func OpenStores( func OpenTokenStores( logger mlogger.Logger, dbf db.Factory, - config *edgeapi.AuthStorageConfig, + config *middleware.AuthStorageConfig, ) (*TokenStores, error) { resolved, err := resolve(logger, dbf, config) if err != nil { @@ -95,7 +96,7 @@ func OpenTokenStores( func resolve( logger mlogger.Logger, dbf db.Factory, - config *edgeapi.AuthStorageConfig, + config *middleware.AuthStorageConfig, ) (*resolvedConfig, error) { deps, err := edgemongo.ResolveDependencies(dbf) if err != nil { diff --git a/backend/config.example.yml b/backend/product/config.example.yml similarity index 100% rename from backend/config.example.yml rename to backend/product/config.example.yml diff --git a/backend/edge/product/interface/accountservice/internal/customer_test.go b/backend/product/customers/internal/customer_test.go similarity index 70% rename from backend/edge/product/interface/accountservice/internal/customer_test.go rename to backend/product/customers/internal/customer_test.go index b6f0387..16fec93 100644 --- a/backend/edge/product/interface/accountservice/internal/customer_test.go +++ b/backend/product/customers/internal/customer_test.go @@ -2,25 +2,26 @@ package accountserviceimp import ( "context" - "net/url" - "testing" - "time" - - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - edgemongo "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/mongo" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/verification" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/auth/config" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" + "git.sendico.io/tech/sxchnge/product/verification/store" + "git.sendico.io/tech/sxchnge/shared/database" + edgemongo "git.sendico.io/tech/sxchnge/shared/database/dependencies" + "git.sendico.io/tech/sxchnge/shared/errors" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go/modules/mongodb" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" + "net/url" + "testing" + "time" ) -func customer(login string) *model.Account { - return (&model.AccountData{LoginData: model.LoginData{UserDataBase: model.UserDataBase{Login: login}, Password: "CustomerPassword1!"}, Describable: model.Describable{Name: "Customer"}}).ToAccount() +func customer(login string) *customermodel.Account { + return (&customermodel.AccountData{LoginData: customermodel.LoginData{UserDataBase: customermodel.UserDataBase{Login: login}, Password: "CustomerPassword1!"}, Describable: sharedmodel.Describable{Name: "Customer"}}).ToAccount() } // Uses the actual copied repositories against an empty SXCHNGE database. @@ -39,26 +40,26 @@ func TestCustomerLifecycleWithoutTenancy(t *testing.T) { query.Set("directConnection", "true") endpoint.RawQuery = query.Encode() uri = endpoint.String() - factory, err := db.NewConnection(zap.NewNop(), &db.Config{Driver: db.Mongo, Settings: model.SettingsT{"uri": uri, "database": "sxchnge_customers"}}) + factory, err := db.NewConnection(zap.NewNop(), &db.Config{Driver: db.Mongo, Settings: sharedmodel.SettingsT{"uri": uri, "database": "sxchnge_customers"}}) require.NoError(t, err) t.Cleanup(factory.CloseConnection) // Custom collections prove lifecycle and login storage select the same data. - config := &api.AuthStorageConfig{AccountsCollection: "customers", VerificationTokensCollection: "customer_verification", RefreshTokensCollection: "customer_sessions"} + config := &middleware.AuthStorageConfig{AccountsCollection: "customers", VerificationTokensCollection: "customer_verification", RefreshTokensCollection: "customer_sessions"} svc, err := NewAccountService(zap.NewNop(), factory, &middleware.PasswordConfig{Check: middleware.PasswordChecks{MinLength: 12}}, config) require.NoError(t, err) first, second := customer("first@example.test"), customer("second@example.test") firstToken, err := svc.CreateAccount(ctx, first) require.NoError(t, err) require.NotEmpty(t, firstToken) - require.Equal(t, model.AccountPendingVerification, first.Status) + require.Equal(t, customermodel.AccountPendingVerification, first.Status) require.True(t, first.MatchPassword("CustomerPassword1!")) _, err = svc.CreateAccount(ctx, second) require.NoError(t, err) require.NotEqual(t, first.ID, second.ID) // Activation tokens cannot be used as another customer's token. - _, err = svc.vdb.Consume(ctx, second.ID, model.PurposeAccountActivation, "", firstToken) + _, err = svc.vdb.Consume(ctx, second.ID, verificationmodel.PurposeAccountActivation, "", firstToken) require.Error(t, err) - _, err = svc.vdb.Consume(ctx, first.ID, model.PurposeAccountActivation, "", firstToken) + _, err = svc.vdb.Consume(ctx, first.ID, verificationmodel.PurposeAccountActivation, "", firstToken) require.NoError(t, err) _, err = svc.CreateAccount(ctx, customer("first@example.test")) require.ErrorIs(t, err, merrors.ErrDataConflict) @@ -68,14 +69,14 @@ func TestCustomerLifecycleWithoutTenancy(t *testing.T) { require.ErrorIs(t, err, merrors.ErrInvalidArg) require.ErrorIs(t, svc.DeleteAccount(ctx, bson.NilObjectID), merrors.ErrInvalidArg) - for _, c := range []*model.Account{first, second} { - require.NoError(t, svc.sessions.Create(ctx, &model.RefreshToken{AccountRef: &c.ID, ExpiresAt: time.Now().Add(time.Hour), ClientRefreshToken: model.ClientRefreshToken{SessionIdentifier: model.SessionIdentifier{ClientID: "web", DeviceID: "device", SessionVersion: "v1"}, RefreshToken: c.ID.Hex()}})) + for _, c := range []*customermodel.Account{first, second} { + require.NoError(t, svc.sessions.Create(ctx, &sessionmodel.RefreshToken{AccountRef: &c.ID, ExpiresAt: time.Now().Add(time.Hour), ClientRefreshToken: sessionmodel.ClientRefreshToken{SessionIdentifier: sessionmodel.SessionIdentifier{ClientID: "web", DeviceID: "device", SessionVersion: "v1"}, RefreshToken: c.ID.Hex()}})) } require.NoError(t, svc.DeleteAccount(ctx, first.ID)) - var stored model.Account + var stored customermodel.Account require.ErrorIs(t, svc.accountDB.Get(ctx, first.ID, &stored), merrors.ErrNoData) require.NoError(t, svc.accountDB.Get(ctx, second.ID, &stored)) - session := &model.SessionIdentifier{ClientID: "web", DeviceID: "device", SessionVersion: "v1"} + session := &sessionmodel.SessionIdentifier{ClientID: "web", DeviceID: "device", SessionVersion: "v1"} _, err = svc.sessions.GetSession(ctx, first.ID, session) require.Error(t, err) _, err = svc.sessions.GetSession(ctx, second.ID, session) diff --git a/backend/edge/product/interface/accountservice/internal/service.go b/backend/product/customers/internal/service.go similarity index 80% rename from backend/edge/product/interface/accountservice/internal/service.go rename to backend/product/customers/internal/service.go index ef82c0e..cf73553 100644 --- a/backend/edge/product/interface/accountservice/internal/service.go +++ b/backend/product/customers/internal/service.go @@ -4,21 +4,21 @@ import ( "context" "errors" "fmt" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "time" "unicode" - authstorage "git.sendico.io/tech/sxchnge/bff/edge/shared/auth/storage" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/verification" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/product/auth/config" + authstorage "git.sendico.io/tech/sxchnge/product/auth/storage" + "git.sendico.io/tech/sxchnge/product/customers/store" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/product/verification/store" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) @@ -33,7 +33,7 @@ type service struct { transactions transaction.Factory } -func validateUserRequest(u *model.Account) error { +func validateUserRequest(u *customermodel.Account) error { if u == nil { return merrors.InvalidArgument("account is required") } @@ -96,19 +96,19 @@ func (s *service) ValidatePassword( return nil } -func (s *service) ValidateAccount(acct *model.Account) error { +func (s *service) ValidateAccount(acct *customermodel.Account) error { if err := validateUserRequest(acct); err != nil { s.logger.Warn("Invalid signup account received", zap.Error(err)) return err } if err := s.ValidatePassword(acct.Password, nil); err != nil { - s.logger.Warn("Password validation failed", zap.Error(err), mzap.Login(acct)) + s.logger.Warn("Password validation failed", zap.Error(err), mzap.Email(acct.Login)) return err } if err := acct.HashPassword(); err != nil { - s.logger.Warn("Failed to hash password", zap.Error(err), mzap.Login(acct)) + s.logger.Warn("Failed to hash password", zap.Error(err), mzap.Email(acct.Login)) return err } @@ -117,7 +117,7 @@ func (s *service) ValidateAccount(acct *model.Account) error { func (s *service) CreateAccount( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, ) (string, error) { if acct == nil { return "", merrors.InvalidArgument("account is required") @@ -129,9 +129,9 @@ func (s *service) CreateAccount( if err := s.ValidateAccount(candidate); err != nil { return "", err } - candidate.Status = model.AccountPendingVerification + candidate.Status = customermodel.AccountPendingVerification // The customer and activation token either both commit or neither does. - var saved *model.Account + var saved *customermodel.Account result, err := s.transactions.CreateTransaction().Execute(ctx, func(tx context.Context) (any, error) { saved = candidate.Copy() if err := s.accountDB.Create(tx, saved); err != nil { @@ -158,14 +158,14 @@ func (s *service) CreateAccount( func (s *service) VerifyAccount( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, ) (string, error) { if acct == nil || acct.ID.IsZero() { return "", merrors.InvalidArgument("customer reference is required") } token, err := s.vdb.Create( ctx, - verification.NewLinkRequest(*acct.GetID(), model.PurposeAccountActivation, ""). + verification.NewLinkRequest(*acct.GetID(), verificationmodel.PurposeAccountActivation, ""). WithTTL(time.Hour*24), ) if err != nil { @@ -200,21 +200,21 @@ func (s *service) DeleteAccount( func (s *service) ResetPassword( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, ) (string, error) { if acct == nil || acct.ID.IsZero() { return "", merrors.InvalidArgument("customer reference is required") } return s.vdb.Create( ctx, - verification.NewOTPRequest(*acct.GetID(), model.PurposePasswordReset, ""). + verification.NewOTPRequest(*acct.GetID(), verificationmodel.PurposePasswordReset, ""). WithTTL(time.Hour), ) } func (s *service) UpdateLogin( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, newLogin string, ) (string, error) { if acct == nil || acct.ID.IsZero() { @@ -222,7 +222,7 @@ func (s *service) UpdateLogin( } return s.vdb.Create( ctx, - verification.NewOTPRequest(*acct.GetID(), model.PurposeEmailChange, newLogin). + verification.NewOTPRequest(*acct.GetID(), verificationmodel.PurposeEmailChange, newLogin). WithTTL(time.Hour), ) } @@ -231,7 +231,7 @@ func NewAccountService( l mlogger.Logger, dbf db.Factory, config *middleware.PasswordConfig, - storageConfig *api.AuthStorageConfig, + storageConfig *middleware.AuthStorageConfig, ) (*service, error) { logger := l.Named("account_service") diff --git a/backend/edge/product/interface/accountservice/internal/service_test.go b/backend/product/customers/internal/service_test.go similarity index 82% rename from backend/edge/product/interface/accountservice/internal/service_test.go rename to backend/product/customers/internal/service_test.go index 467981d..9a1119b 100644 --- a/backend/edge/product/interface/accountservice/internal/service_test.go +++ b/backend/product/customers/internal/service_test.go @@ -1,10 +1,11 @@ package accountserviceimp import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" "testing" - apiconfig "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + apiconfig "git.sendico.io/tech/sxchnge/product/auth/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap" @@ -93,14 +94,14 @@ func TestValidateAccount(t *testing.T) { } t.Run("ValidAccount", func(t *testing.T) { - account := &model.Account{ - AccountPublic: model.AccountPublic{ - AccountBase: model.AccountBase{ - Describable: model.Describable{ + account := &customermodel.Account{ + AccountPublic: customermodel.AccountPublic{ + AccountBase: customermodel.AccountBase{ + Describable: sharedmodel.Describable{ Name: "Test User", }, }, - UserDataBase: model.UserDataBase{ + UserDataBase: customermodel.UserDataBase{ Login: "test@example.com", }, }, @@ -116,9 +117,9 @@ func TestValidateAccount(t *testing.T) { }) t.Run("AccountMissingName", func(t *testing.T) { - account := &model.Account{ - AccountPublic: model.AccountPublic{ - UserDataBase: model.UserDataBase{ + account := &customermodel.Account{ + AccountPublic: customermodel.AccountPublic{ + UserDataBase: customermodel.UserDataBase{ Login: "test@example.com", }, }, @@ -131,10 +132,10 @@ func TestValidateAccount(t *testing.T) { }) t.Run("AccountMissingLogin", func(t *testing.T) { - account := &model.Account{ - AccountPublic: model.AccountPublic{ - AccountBase: model.AccountBase{ - Describable: model.Describable{ + account := &customermodel.Account{ + AccountPublic: customermodel.AccountPublic{ + AccountBase: customermodel.AccountBase{ + Describable: sharedmodel.Describable{ Name: "Test User", }, }, @@ -148,14 +149,14 @@ func TestValidateAccount(t *testing.T) { }) t.Run("AccountMissingPassword", func(t *testing.T) { - account := &model.Account{ - AccountPublic: model.AccountPublic{ - AccountBase: model.AccountBase{ - Describable: model.Describable{ + account := &customermodel.Account{ + AccountPublic: customermodel.AccountPublic{ + AccountBase: customermodel.AccountBase{ + Describable: sharedmodel.Describable{ Name: "Test User", }, }, - UserDataBase: model.UserDataBase{ + UserDataBase: customermodel.UserDataBase{ Login: "test@example.com", }, }, @@ -168,14 +169,14 @@ func TestValidateAccount(t *testing.T) { }) t.Run("AccountInvalidPassword", func(t *testing.T) { - account := &model.Account{ - AccountPublic: model.AccountPublic{ - AccountBase: model.AccountBase{ - Describable: model.Describable{ + account := &customermodel.Account{ + AccountPublic: customermodel.AccountPublic{ + AccountBase: customermodel.AccountBase{ + Describable: sharedmodel.Describable{ Name: "Test User", }, }, - UserDataBase: model.UserDataBase{ + UserDataBase: customermodel.UserDataBase{ Login: "test@example.com", }, }, diff --git a/backend/pkg/model/account.go b/backend/product/customers/model/account.go similarity index 71% rename from backend/pkg/model/account.go rename to backend/product/customers/model/account.go index a7d331a..6655217 100644 --- a/backend/pkg/model/account.go +++ b/backend/product/customers/model/account.go @@ -1,8 +1,9 @@ package model import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/record" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" + "git.sendico.io/tech/sxchnge/shared/service" "golang.org/x/crypto/bcrypt" ) @@ -17,11 +18,11 @@ const ( ) type AccountBase struct { - storable.Base `bson:",inline" json:",inline"` - ArchivableBase `bson:",inline" json:",inline"` - Describable `bson:",inline" json:",inline"` - LastName string `bson:"lastName" json:"lastName"` - AvatarURL *string `bson:"avatarUrl,omitempty" json:"avatarUrl,omitempty"` + storable.Base `bson:",inline" json:",inline"` + sharedmodel.ArchivableBase `bson:",inline" json:",inline"` + sharedmodel.Describable `bson:",inline" json:",inline"` + LastName string `bson:"lastName" json:"lastName"` + AvatarURL *string `bson:"avatarUrl,omitempty" json:"avatarUrl,omitempty"` } func (*AccountBase) Collection() string { diff --git a/backend/pkg/model/userdata.go b/backend/product/customers/model/userdata.go similarity index 69% rename from backend/pkg/model/userdata.go rename to backend/product/customers/model/userdata.go index 8a56bd1..22d1b36 100644 --- a/backend/pkg/model/userdata.go +++ b/backend/product/customers/model/userdata.go @@ -1,5 +1,7 @@ package model +import sharedmodel "git.sendico.io/tech/sxchnge/shared/model" + type UserDataBase struct { Login string `bson:"login" json:"login"` Locale string `bson:"locale" json:"locale"` @@ -11,9 +13,9 @@ type LoginData struct { } type AccountData struct { - LoginData `bson:",inline" json:",inline"` - Describable `bson:",inline" json:",inline"` - LastName string `bson:"lastName" json:"lastName"` + LoginData `bson:",inline" json:",inline"` + sharedmodel.Describable `bson:",inline" json:",inline"` + LastName string `bson:"lastName" json:"lastName"` } func (ad *AccountData) ToAccount() *Account { diff --git a/backend/product/customers/store/account.go b/backend/product/customers/store/account.go new file mode 100644 index 0000000..59dfaf9 --- /dev/null +++ b/backend/product/customers/store/account.go @@ -0,0 +1,14 @@ +package account + +import ( + "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + + "git.sendico.io/tech/sxchnge/shared/database/template" +) + +// DB is the interface which must be implemented by all db drivers +type DB interface { + template.DB[*customermodel.Account] + GetByEmail(ctx context.Context, email string) (*customermodel.Account, error) +} diff --git a/backend/pkg/db/internal/mongo/accountdb/db.go b/backend/product/customers/store/mongo/db.go similarity index 67% rename from backend/pkg/db/internal/mongo/accountdb/db.go rename to backend/product/customers/store/mongo/db.go index c83bf6b..58dd464 100644 --- a/backend/pkg/db/internal/mongo/accountdb/db.go +++ b/backend/product/customers/store/mongo/db.go @@ -1,19 +1,19 @@ package accountdb import ( + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" "strings" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/database/template" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/mongo" "go.uber.org/zap" ) type AccountDB struct { - template.DBImp[*model.Account] + template.DBImp[*customermodel.Account] } func Create(logger mlogger.Logger, db *mongo.Database) (*AccountDB, error) { @@ -28,7 +28,7 @@ func CreateWithCollection(logger mlogger.Logger, db *mongo.Database, collection } p := &AccountDB{ - DBImp: *template.Create[*model.Account](logger, collection, db), + DBImp: *template.Create[*customermodel.Account](logger, collection, db), } if err := p.Repository.CreateIndex(&ri.Definition{ diff --git a/backend/product/customers/store/mongo/user.go b/backend/product/customers/store/mongo/user.go new file mode 100644 index 0000000..c59bd68 --- /dev/null +++ b/backend/product/customers/store/mongo/user.go @@ -0,0 +1,13 @@ +package accountdb + +import ( + "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + + "git.sendico.io/tech/sxchnge/shared/database/repository" +) + +func (db *AccountDB) GetByEmail(ctx context.Context, email string) (*customermodel.Account, error) { + var account customermodel.Account + return &account, db.FindOne(ctx, repository.Filter("login", email), &account) +} diff --git a/backend/edge/product/interface/accountservice/types.go b/backend/product/customers/types.go similarity index 70% rename from backend/edge/product/interface/accountservice/types.go rename to backend/product/customers/types.go index fa2807f..fa3b0e7 100644 --- a/backend/edge/product/interface/accountservice/types.go +++ b/backend/product/customers/types.go @@ -2,13 +2,12 @@ package accountservice import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" - accountserviceimp "git.sendico.io/tech/sxchnge/bff/edge/product/interface/accountservice/internal" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api" - "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware" - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/auth/config" + accountserviceimp "git.sendico.io/tech/sxchnge/product/customers/internal" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/logging" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -25,7 +24,7 @@ type AccountService interface { // 1) generate reset password token ResetPassword( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, ) (verificationToken string, err error) // CreateAccount will: @@ -33,17 +32,17 @@ type AccountService interface { // 2) issue an activation token CreateAccount( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, ) (verificationToken string, err error) VerifyAccount( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, ) (verificationToken string, err error) UpdateLogin( ctx context.Context, - acct *model.Account, + acct *customermodel.Account, newLogin string, ) (verificationToken string, err error) @@ -58,7 +57,7 @@ func NewAccountService( logger mlogger.Logger, dbf db.Factory, config *middleware.PasswordConfig, - storageConfig *api.AuthStorageConfig, + storageConfig *middleware.AuthStorageConfig, ) (AccountService, error) { return accountserviceimp.NewAccountService(logger, dbf, config, storageConfig) } diff --git a/backend/go.mod b/backend/product/go.mod similarity index 94% rename from backend/go.mod rename to backend/product/go.mod index faf3ea6..ce09755 100644 --- a/backend/go.mod +++ b/backend/product/go.mod @@ -1,26 +1,7 @@ -module git.sendico.io/tech/sxchnge/bff +module git.sendico.io/tech/sxchnge/product go 1.27.0 -require ( - github.com/go-chi/chi/v5 v5.3.2 - github.com/go-chi/cors v1.2.2 - github.com/go-chi/jwtauth/v5 v5.4.0 - github.com/go-chi/metrics v0.1.1 - github.com/go-viper/mapstructure/v2 v2.5.0 - github.com/google/uuid v1.6.0 - github.com/mattn/go-colorable v0.1.15 - github.com/prometheus/client_golang v1.24.1 - github.com/stretchr/testify v1.12.1 - github.com/testcontainers/testcontainers-go v0.44.0 - github.com/testcontainers/testcontainers-go/modules/mongodb v0.44.0 - go.mongodb.org/mongo-driver/v2 v2.9.0 - go.uber.org/zap v1.28.0 - golang.org/x/crypto v0.56.0 - gopkg.in/yaml.v3 v3.0.1 - moul.io/chizap v1.0.3 -) - require ( dario.cat/mergo v1.0.2 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect @@ -43,6 +24,7 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/klauspost/compress v1.19.2 // indirect github.com/lestrrat-go/blackmagic v1.0.3 // indirect @@ -52,6 +34,7 @@ require ( github.com/lestrrat-go/option v1.0.1 // indirect github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/go-archive v0.2.0 // indirect @@ -91,3 +74,23 @@ require ( golang.org/x/text v0.41.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) + +require ( + git.sendico.io/tech/sxchnge/shared v0.0.0 + github.com/go-chi/chi/v5 v5.3.2 + github.com/go-chi/cors v1.2.2 + github.com/go-chi/jwtauth/v5 v5.4.0 + github.com/go-chi/metrics v0.1.1 + github.com/google/uuid v1.6.0 + github.com/prometheus/client_golang v1.24.1 + github.com/stretchr/testify v1.12.1 + github.com/testcontainers/testcontainers-go v0.44.0 + github.com/testcontainers/testcontainers-go/modules/mongodb v0.44.0 + go.mongodb.org/mongo-driver/v2 v2.9.0 + go.uber.org/zap v1.28.0 + golang.org/x/crypto v0.56.0 + gopkg.in/yaml.v3 v3.0.1 + moul.io/chizap v1.0.3 +) + +replace git.sendico.io/tech/sxchnge/shared => ../shared diff --git a/backend/go.sum b/backend/product/go.sum similarity index 100% rename from backend/go.sum rename to backend/product/go.sum diff --git a/backend/product/main.go b/backend/product/main.go new file mode 100644 index 0000000..e0e04a9 --- /dev/null +++ b/backend/product/main.go @@ -0,0 +1,21 @@ +// Adapted from Sendico api/edge/product/main.go; see ../PROVENANCE.md. +package main + +import ( + application "git.sendico.io/tech/sxchnge/product/app/process" + smain "git.sendico.io/tech/sxchnge/product/app/process/run" + "git.sendico.io/tech/sxchnge/product/app/profile" + "git.sendico.io/tech/sxchnge/product/app/server" + "git.sendico.io/tech/sxchnge/product/app/version" + "git.sendico.io/tech/sxchnge/shared/logging" + "os" +) + +func factory(logger mlogger.Logger, file string, debug bool) (application.Application, error) { + return server.Create(logger, file, debug, profile.Runtime()) +} +func main() { + if smain.RunServer("sxchnge.bff", appversion.Create(), factory) != nil { + os.Exit(1) + } +} diff --git a/backend/edge/shared/auth/browser/browser.go b/backend/product/sessions/browser/browser.go similarity index 98% rename from backend/edge/shared/auth/browser/browser.go rename to backend/product/sessions/browser/browser.go index 14bc11d..29ec15a 100644 --- a/backend/edge/shared/auth/browser/browser.go +++ b/backend/product/sessions/browser/browser.go @@ -8,7 +8,7 @@ import ( "strings" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" ) const ( diff --git a/backend/edge/shared/auth/browser/browser_test.go b/backend/product/sessions/browser/browser_test.go similarity index 100% rename from backend/edge/shared/auth/browser/browser_test.go rename to backend/product/sessions/browser/browser_test.go diff --git a/backend/edge/shared/auth/sessionmetrics/metrics.go b/backend/product/sessions/metrics/metrics.go similarity index 100% rename from backend/edge/shared/auth/sessionmetrics/metrics.go rename to backend/product/sessions/metrics/metrics.go diff --git a/backend/pkg/model/client.go b/backend/product/sessions/model/client.go similarity index 89% rename from backend/pkg/model/client.go rename to backend/product/sessions/model/client.go index dd43e40..4958803 100644 --- a/backend/pkg/model/client.go +++ b/backend/product/sessions/model/client.go @@ -1,8 +1,8 @@ package model import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/model/refresh.go b/backend/product/sessions/model/refresh.go similarity index 88% rename from backend/pkg/model/refresh.go rename to backend/product/sessions/model/refresh.go index bc9c8ee..9c6cb31 100644 --- a/backend/pkg/model/refresh.go +++ b/backend/product/sessions/model/refresh.go @@ -3,8 +3,8 @@ package model import ( "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/model/sessionid.go b/backend/product/sessions/model/sessionid.go similarity index 100% rename from backend/pkg/model/sessionid.go rename to backend/product/sessions/model/sessionid.go diff --git a/backend/edge/shared/storage/refreshtokens/client.go b/backend/product/sessions/store/client.go similarity index 58% rename from backend/edge/shared/storage/refreshtokens/client.go rename to backend/product/sessions/store/client.go index 844d327..9810fc5 100644 --- a/backend/edge/shared/storage/refreshtokens/client.go +++ b/backend/product/sessions/store/client.go @@ -2,11 +2,10 @@ package refreshtokens import ( "context" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" ) -func (db *RefreshTokenDB) GetClient(ctx context.Context, clientID string) (*model.Client, error) { - var client model.Client +func (db *RefreshTokenDB) GetClient(ctx context.Context, clientID string) (*sessionmodel.Client, error) { + var client sessionmodel.Client return &client, db.clients.FindOneByFilter(ctx, filterByClientId(clientID), &client) } diff --git a/backend/edge/shared/storage/refreshtokens/crud.go b/backend/product/sessions/store/crud.go similarity index 84% rename from backend/edge/shared/storage/refreshtokens/crud.go rename to backend/product/sessions/store/crud.go index cef1040..ef99024 100644 --- a/backend/edge/shared/storage/refreshtokens/crud.go +++ b/backend/product/sessions/store/crud.go @@ -3,19 +3,19 @@ package refreshtokens import ( "context" "errors" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging/fields" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) -func (db *RefreshTokenDB) Create(ctx context.Context, rt *model.RefreshToken) error { +func (db *RefreshTokenDB) Create(ctx context.Context, rt *sessionmodel.RefreshToken) error { if rt.LastUsedAt.IsZero() { rt.LastUsedAt = time.Now() } @@ -54,7 +54,7 @@ func (db *RefreshTokenDB) Create(ctx context.Context, rt *model.RefreshToken) er return nil } -func (db *RefreshTokenDB) replaceActive(ctx context.Context, rt *model.RefreshToken) (bool, error) { +func (db *RefreshTokenDB) replaceActive(ctx context.Context, rt *sessionmodel.RefreshToken) (bool, error) { patch := repository.Patch(). Set(repository.Field(TokenField), rt.RefreshToken). Set(repository.Field(ExpiresAtField), rt.ExpiresAt). @@ -64,7 +64,7 @@ func (db *RefreshTokenDB) replaceActive(ctx context.Context, rt *model.RefreshTo Set(repository.Field(IsRevokedField), rt.IsRevoked). Set(repository.Field(SessionVersionField), rt.SessionVersion) - var stored model.RefreshToken + var stored sessionmodel.RefreshToken matched, err := db.Repository.PatchOneByFilter( ctx, filterSlotByAccount(*rt.AccountRef, &rt.SessionIdentifier), @@ -86,7 +86,7 @@ func (db *RefreshTokenDB) replaceActive(ctx context.Context, rt *model.RefreshTo return true, nil } -func (db *RefreshTokenDB) Update(ctx context.Context, rt *model.RefreshToken) error { +func (db *RefreshTokenDB) Update(ctx context.Context, rt *sessionmodel.RefreshToken) error { rt.LastUsedAt = time.Now() // Use Patch instead of Update to avoid race conditions @@ -107,8 +107,8 @@ func (db *RefreshTokenDB) Delete(ctx context.Context, tokenRef bson.ObjectID) er return db.DBImp.Delete(ctx, tokenRef) } -func (db *RefreshTokenDB) Revoke(ctx context.Context, accountRef bson.ObjectID, session *model.SessionIdentifier) error { - var rt model.RefreshToken +func (db *RefreshTokenDB) Revoke(ctx context.Context, accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) error { + var rt sessionmodel.RefreshToken f := filterByAccount(accountRef, session) if err := db.Repository.FindOneByFilter(ctx, f, &rt); err != nil { if errors.Is(err, merrors.ErrNoData) { @@ -127,7 +127,7 @@ func (db *RefreshTokenDB) Revoke(ctx context.Context, accountRef bson.ObjectID, return db.Patch(ctx, *rt.GetID(), patch) } -func (db *RefreshTokenDB) RevokeToken(ctx context.Context, token *model.ClientRefreshToken) (bool, error) { +func (db *RefreshTokenDB) RevokeToken(ctx context.Context, token *sessionmodel.ClientRefreshToken) (bool, error) { query := filter(&token.SessionIdentifier). And(repository.Query().Comparison(repository.Field(TokenField), builder.Eq, token.RefreshToken)) patch := repository.Patch(). @@ -137,8 +137,8 @@ func (db *RefreshTokenDB) RevokeToken(ctx context.Context, token *model.ClientRe return modified > 0, err } -func (db *RefreshTokenDB) GetSession(ctx context.Context, accountRef bson.ObjectID, session *model.SessionIdentifier) (*model.RefreshToken, error) { - var rt model.RefreshToken +func (db *RefreshTokenDB) GetSession(ctx context.Context, accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) (*sessionmodel.RefreshToken, error) { + var rt sessionmodel.RefreshToken if err := db.Repository.FindOneByFilter(ctx, filterByAccount(accountRef, session), &rt); err != nil { return nil, err } @@ -148,8 +148,8 @@ func (db *RefreshTokenDB) GetSession(ctx context.Context, accountRef bson.Object return &rt, nil } -func (db *RefreshTokenDB) GetByCRT(ctx context.Context, t *model.ClientRefreshToken) (*model.RefreshToken, error) { - var rt model.RefreshToken +func (db *RefreshTokenDB) GetByCRT(ctx context.Context, t *sessionmodel.ClientRefreshToken) (*sessionmodel.RefreshToken, error) { + var rt sessionmodel.RefreshToken f := filter(&t.SessionIdentifier).And(repository.Query().Filter(repository.Field("token"), t.RefreshToken)) if err := db.Repository.FindOneByFilter(ctx, f, &rt); err != nil { if !errors.Is(err, merrors.ErrNoData) { diff --git a/backend/edge/shared/storage/refreshtokens/crud_test.go b/backend/product/sessions/store/crud_test.go similarity index 85% rename from backend/edge/shared/storage/refreshtokens/crud_test.go rename to backend/product/sessions/store/crud_test.go index 7a2173a..4e5e97c 100644 --- a/backend/edge/shared/storage/refreshtokens/crud_test.go +++ b/backend/product/sessions/store/crud_test.go @@ -3,15 +3,15 @@ package refreshtokens import ( "context" "errors" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/template" + "git.sendico.io/tech/sxchnge/shared/errors" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" @@ -58,17 +58,17 @@ func (r *createRepository) Insert(_ context.Context, object storable.Storable, _ func newCreateDB(repo repository.Repository) *RefreshTokenDB { return &RefreshTokenDB{ - DBImp: template.DBImp[*model.RefreshToken]{ + DBImp: template.DBImp[*sessionmodel.RefreshToken]{ Logger: zap.NewNop(), Repository: repo, }, } } -func versionedToken(accountRef bson.ObjectID, version, value string) *model.RefreshToken { - return &model.RefreshToken{ - ClientRefreshToken: model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ +func versionedToken(accountRef bson.ObjectID, version, value string) *sessionmodel.RefreshToken { + return &sessionmodel.RefreshToken{ + ClientRefreshToken: sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: "web-app", DeviceID: "browser", SessionVersion: version, @@ -150,7 +150,7 @@ func TestCreatePreservesUnrelatedInsertConflict(t *testing.T) { } func TestCredentialFilterKeepsSessionVersion(t *testing.T) { - query := filterByAccount(bson.NewObjectID(), &model.SessionIdentifier{ + query := filterByAccount(bson.NewObjectID(), &sessionmodel.SessionIdentifier{ ClientID: "web-app", DeviceID: "browser", SessionVersion: "version-2", diff --git a/backend/edge/shared/storage/refreshtokens/db.go b/backend/product/sessions/store/db.go similarity index 83% rename from backend/edge/shared/storage/refreshtokens/db.go rename to backend/product/sessions/store/db.go index f6be858..8b9549a 100644 --- a/backend/edge/shared/storage/refreshtokens/db.go +++ b/backend/product/sessions/store/db.go @@ -1,14 +1,14 @@ package refreshtokens import ( + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "strings" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/repository" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/database/template" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/mongo" "go.uber.org/zap" ) @@ -19,7 +19,7 @@ type Collections struct { } type RefreshTokenDB struct { - template.DBImp[*model.RefreshToken] + template.DBImp[*sessionmodel.RefreshToken] clients repository.Repository collection Collections } @@ -41,7 +41,7 @@ func (c Collections) WithDefaults() Collections { func CreateWithCollections(logger mlogger.Logger, db *mongo.Database, collections Collections) (*RefreshTokenDB, error) { collections = collections.WithDefaults() p := &RefreshTokenDB{ - DBImp: *template.Create[*model.RefreshToken](logger, mservice.Type(collections.RefreshTokens), db), + DBImp: *template.Create[*sessionmodel.RefreshToken](logger, mservice.Type(collections.RefreshTokens), db), clients: repository.CreateMongoRepository(db, collections.Clients), collection: collections, } diff --git a/backend/edge/shared/storage/refreshtokens/fields.go b/backend/product/sessions/store/fields.go similarity index 100% rename from backend/edge/shared/storage/refreshtokens/fields.go rename to backend/product/sessions/store/fields.go diff --git a/backend/edge/shared/storage/refreshtokens/filters.go b/backend/product/sessions/store/filters.go similarity index 62% rename from backend/edge/shared/storage/refreshtokens/filters.go rename to backend/product/sessions/store/filters.go index 6a04ccf..d204277 100644 --- a/backend/edge/shared/storage/refreshtokens/filters.go +++ b/backend/product/sessions/store/filters.go @@ -1,9 +1,9 @@ package refreshtokens import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -11,7 +11,7 @@ func filterByClientId(clientID string) builder.Query { return repository.Query().Comparison(repository.Field("clientId"), builder.Eq, clientID) } -func filterSlot(session *model.SessionIdentifier) builder.Query { +func filterSlot(session *sessionmodel.SessionIdentifier) builder.Query { filter := filterByClientId(session.ClientID) filter.And( repository.Query().Comparison(repository.Field("deviceId"), builder.Eq, session.DeviceID), @@ -20,7 +20,7 @@ func filterSlot(session *model.SessionIdentifier) builder.Query { return filter } -func filter(session *model.SessionIdentifier) builder.Query { +func filter(session *sessionmodel.SessionIdentifier) builder.Query { filter := filterSlot(session) if session.SessionVersion != "" { filter.And(repository.Query().Comparison(repository.Field("sessionVersion"), builder.Eq, session.SessionVersion)) @@ -28,10 +28,10 @@ func filter(session *model.SessionIdentifier) builder.Query { return filter } -func filterByAccount(accountRef bson.ObjectID, session *model.SessionIdentifier) builder.Query { +func filterByAccount(accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) builder.Query { return filter(session).And(repository.Query().Comparison(repository.AccountField(), builder.Eq, accountRef)) } -func filterSlotByAccount(accountRef bson.ObjectID, session *model.SessionIdentifier) builder.Query { +func filterSlotByAccount(accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) builder.Query { return filterSlot(session).And(repository.Query().Comparison(repository.AccountField(), builder.Eq, accountRef)) } diff --git a/backend/product/sessions/store/refreshtokens.go b/backend/product/sessions/store/refreshtokens.go new file mode 100644 index 0000000..4928367 --- /dev/null +++ b/backend/product/sessions/store/refreshtokens.go @@ -0,0 +1,19 @@ +package refreshtokens + +import ( + "context" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" + + "git.sendico.io/tech/sxchnge/shared/database/template" + "go.mongodb.org/mongo-driver/v2/bson" +) + +type DB interface { + template.DB[*sessionmodel.RefreshToken] + Revoke(ctx context.Context, accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) error + RevokeToken(ctx context.Context, token *sessionmodel.ClientRefreshToken) (bool, error) + RevokeAll(ctx context.Context, accountRef bson.ObjectID, deviceID string) error + GetSession(ctx context.Context, accountRef bson.ObjectID, session *sessionmodel.SessionIdentifier) (*sessionmodel.RefreshToken, error) + GetByCRT(ctx context.Context, t *sessionmodel.ClientRefreshToken) (*sessionmodel.RefreshToken, error) + GetClient(ctx context.Context, clientID string) (*sessionmodel.Client, error) +} diff --git a/backend/edge/shared/storage/refreshtokens/refreshtokens_indexes_test.go b/backend/product/sessions/store/refreshtokens_indexes_test.go similarity index 92% rename from backend/edge/shared/storage/refreshtokens/refreshtokens_indexes_test.go rename to backend/product/sessions/store/refreshtokens_indexes_test.go index 9ac328b..44ec66d 100644 --- a/backend/edge/shared/storage/refreshtokens/refreshtokens_indexes_test.go +++ b/backend/product/sessions/store/refreshtokens_indexes_test.go @@ -8,9 +8,9 @@ import ( "fmt" "testing" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" diff --git a/backend/edge/shared/storage/refreshtokens/refreshtokens_misc_test.go b/backend/product/sessions/store/refreshtokens_misc_test.go similarity index 84% rename from backend/edge/shared/storage/refreshtokens/refreshtokens_misc_test.go rename to backend/product/sessions/store/refreshtokens_misc_test.go index f822226..3a0c9fb 100644 --- a/backend/edge/shared/storage/refreshtokens/refreshtokens_misc_test.go +++ b/backend/product/sessions/store/refreshtokens_misc_test.go @@ -6,11 +6,11 @@ package refreshtokens_test import ( "context" "errors" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" @@ -35,8 +35,8 @@ func TestRefreshTokenDB_ClientManagement(t *testing.T) { err = db.Create(ctx, mobileToken) require.NoError(t, err) - webCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + webCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: "web-app", DeviceID: "device1", }, @@ -48,8 +48,8 @@ func TestRefreshTokenDB_ClientManagement(t *testing.T) { assert.Equal(t, "web-app", retrievedToken.ClientID) assert.Equal(t, "device1", retrievedToken.DeviceID) - mobileCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + mobileCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: "mobile-app", DeviceID: "device2", }, @@ -79,15 +79,15 @@ func TestRefreshTokenDB_SecurityScenarios(t *testing.T) { err := db.Create(ctx, refreshToken) require.NoError(t, err) - session := &model.SessionIdentifier{ + session := &sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, } err = db.Revoke(ctx, userID, session) require.NoError(t, err) - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -100,8 +100,8 @@ func TestRefreshTokenDB_SecurityScenarios(t *testing.T) { }) t.Run("Invalid_Token_Attempts", func(t *testing.T) { - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: "invalid-client", DeviceID: "invalid-device", }, @@ -131,13 +131,13 @@ func TestRefreshTokenDB_ExpiredTokenHandling(t *testing.T) { err := db.Create(ctx, refreshToken) require.NoError(t, err) - var storedToken model.RefreshToken + var storedToken sessionmodel.RefreshToken err = db.Get(ctx, *refreshToken.GetID(), &storedToken) require.NoError(t, err) assert.True(t, storedToken.ExpiresAt.Before(time.Now())) - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -166,8 +166,8 @@ func TestRefreshTokenDB_ConcurrentAccess(t *testing.T) { err := db.Create(ctx, refreshToken) require.NoError(t, err) - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -210,7 +210,7 @@ func TestRefreshTokenDB_EdgeCases(t *testing.T) { err = db.Delete(ctx, tokenID) require.NoError(t, err) - var result model.RefreshToken + var result sessionmodel.RefreshToken err = db.Get(ctx, tokenID, &result) assert.Error(t, err) assert.True(t, errors.Is(err, merrors.ErrNoData)) @@ -218,7 +218,7 @@ func TestRefreshTokenDB_EdgeCases(t *testing.T) { t.Run("Revoke_Non_Existent_Token", func(t *testing.T) { userID := bson.NewObjectID() - session := &model.SessionIdentifier{ + session := &sessionmodel.SessionIdentifier{ ClientID: "non-existent-client", DeviceID: "non-existent-device", } @@ -239,8 +239,8 @@ func TestRefreshTokenDB_EdgeCases(t *testing.T) { err = db.RevokeAll(ctx, userID, deviceID) require.NoError(t, err) - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, diff --git a/backend/edge/shared/storage/refreshtokens/refreshtokens_session_replacement_test.go b/backend/product/sessions/store/refreshtokens_session_replacement_test.go similarity index 83% rename from backend/edge/shared/storage/refreshtokens/refreshtokens_session_replacement_test.go rename to backend/product/sessions/store/refreshtokens_session_replacement_test.go index 0277457..288e84c 100644 --- a/backend/edge/shared/storage/refreshtokens/refreshtokens_session_replacement_test.go +++ b/backend/product/sessions/store/refreshtokens_session_replacement_test.go @@ -6,12 +6,12 @@ package refreshtokens_test import ( "context" "errors" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "testing" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" @@ -45,8 +45,8 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { assert.Equal(t, firstTokenID, *secondToken.GetID()) // First token should no longer work - firstCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + firstCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, SessionVersion: "session-version-1", @@ -57,8 +57,8 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { assert.Error(t, err) // Second token should work - secondCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + secondCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, SessionVersion: "session-version-2", @@ -87,7 +87,7 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { err = db.RevokeAll(ctx, userID, "") require.NoError(t, err) - var revoked model.RefreshToken + var revoked sessionmodel.RefreshToken err = db.Get(ctx, *firstToken.GetID(), &revoked) require.NoError(t, err) assert.True(t, revoked.IsRevoked) @@ -97,8 +97,8 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { err = db.Create(ctx, reissueToken) require.NoError(t, err) - newCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + newCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -108,8 +108,8 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { require.NoError(t, err) // Old token must remain unusable - oldCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + oldCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -141,14 +141,14 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { require.NotNil(t, firstToken.GetID()) firstTokenID := *firstToken.GetID() - session := &model.SessionIdentifier{ + session := &sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, } err = db.Revoke(ctx, userID, session) require.NoError(t, err) - var revoked model.RefreshToken + var revoked sessionmodel.RefreshToken err = db.Get(ctx, firstTokenID, &revoked) require.NoError(t, err) assert.True(t, revoked.IsRevoked) @@ -159,8 +159,8 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { require.NotNil(t, secondToken.GetID()) assert.NotEqual(t, firstTokenID, *secondToken.GetID()) - firstCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + firstCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -170,8 +170,8 @@ func TestRefreshTokenDB_SessionReplacement(t *testing.T) { assert.Error(t, err) assert.True(t, errors.Is(err, merrors.ErrNoData)) - secondCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + secondCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, diff --git a/backend/edge/shared/storage/refreshtokens/refreshtokens_session_test.go b/backend/product/sessions/store/refreshtokens_session_test.go similarity index 86% rename from backend/edge/shared/storage/refreshtokens/refreshtokens_session_test.go rename to backend/product/sessions/store/refreshtokens_session_test.go index 607baf9..d3ce92a 100644 --- a/backend/edge/shared/storage/refreshtokens/refreshtokens_session_test.go +++ b/backend/product/sessions/store/refreshtokens_session_test.go @@ -5,13 +5,12 @@ package refreshtokens_test import ( "context" - "testing" - "time" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" + "testing" + "time" ) func TestRefreshTokenDB_TokenRotation(t *testing.T) { @@ -35,8 +34,8 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) { time.Sleep(10 * time.Millisecond) // Use token - should update LastUsedAt - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -59,8 +58,8 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) { assert.Error(t, err) // New token should work - newCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + newCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -85,9 +84,9 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) { time.Sleep(10 * time.Millisecond) - replacement := &model.RefreshToken{ - ClientRefreshToken: model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + replacement := &sessionmodel.RefreshToken{ + ClientRefreshToken: sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -105,7 +104,7 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) { require.NotNil(t, replacement.GetID()) assert.Equal(t, firstTokenID, *replacement.GetID()) - var stored model.RefreshToken + var stored sessionmodel.RefreshToken err = db.Get(ctx, firstTokenID, &stored) require.NoError(t, err) assert.False(t, stored.LastUsedAt.IsZero()) @@ -128,8 +127,8 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) { time.Sleep(10 * time.Millisecond) - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -140,7 +139,7 @@ func TestRefreshTokenDB_TokenRotation(t *testing.T) { require.NoError(t, err) assert.True(t, retrievedToken.LastUsedAt.After(initialLastUsedAt)) - var stored model.RefreshToken + var stored sessionmodel.RefreshToken err = db.Get(ctx, *refreshToken.GetID(), &stored) require.NoError(t, err) assert.True(t, stored.LastUsedAt.After(initialLastUsedAt)) diff --git a/backend/edge/shared/storage/refreshtokens/refreshtokens_test.go b/backend/product/sessions/store/refreshtokens_test.go similarity index 86% rename from backend/edge/shared/storage/refreshtokens/refreshtokens_test.go rename to backend/product/sessions/store/refreshtokens_test.go index 76a7150..a3b707e 100644 --- a/backend/edge/shared/storage/refreshtokens/refreshtokens_test.go +++ b/backend/product/sessions/store/refreshtokens_test.go @@ -8,13 +8,13 @@ import ( "crypto/sha1" "errors" "fmt" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - factory "git.sendico.io/tech/sxchnge/bff/pkg/mlogger/factory" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/shared/errors" + factory "git.sendico.io/tech/sxchnge/shared/logging/factory" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" @@ -69,10 +69,10 @@ func setupTestDBWithMongo(t *testing.T) (*refreshtokens.RefreshTokenDB, *mongo.D return db, database, cleanup } -func createTestRefreshToken(accountRef bson.ObjectID, clientID, deviceID, token string) *model.RefreshToken { - return &model.RefreshToken{ - ClientRefreshToken: model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ +func createTestRefreshToken(accountRef bson.ObjectID, clientID, deviceID, token string) *sessionmodel.RefreshToken { + return &sessionmodel.RefreshToken{ + ClientRefreshToken: sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -106,8 +106,8 @@ func TestRefreshTokenDB_AuthenticationFlow(t *testing.T) { require.NoError(t, err) // Step 2: User uses refresh token to get new access token - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -122,7 +122,7 @@ func TestRefreshTokenDB_AuthenticationFlow(t *testing.T) { assert.False(t, retrievedToken.IsRevoked) // Step 3: User logs out - revoke the token - session := &model.SessionIdentifier{ + session := &sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, } @@ -153,8 +153,8 @@ func TestRefreshTokenDB_AuthenticationFlow(t *testing.T) { require.NoError(t, err) // Step 3: Try to use revoked token - should fail - crt := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + crt := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: deviceID, }, @@ -197,8 +197,8 @@ func TestRefreshTokenDB_MultiDeviceManagement(t *testing.T) { require.NoError(t, err) // Phone should still work - phoneCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + phoneCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: "phone-ios", }, @@ -208,8 +208,8 @@ func TestRefreshTokenDB_MultiDeviceManagement(t *testing.T) { require.NoError(t, err) // Tablet and desktop should be revoked - tabletCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + tabletCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: "tablet-android", }, @@ -218,8 +218,8 @@ func TestRefreshTokenDB_MultiDeviceManagement(t *testing.T) { _, err = db.GetByCRT(ctx, tabletCRT) assert.Error(t, err) - desktopCRT := &model.ClientRefreshToken{ - SessionIdentifier: model.SessionIdentifier{ + desktopCRT := &sessionmodel.ClientRefreshToken{ + SessionIdentifier: sessionmodel.SessionIdentifier{ ClientID: clientID, DeviceID: "desktop-windows", }, diff --git a/backend/edge/shared/storage/refreshtokens/revoke.go b/backend/product/sessions/store/revoke.go similarity index 84% rename from backend/edge/shared/storage/refreshtokens/revoke.go rename to backend/product/sessions/store/revoke.go index a704af8..0d0232c 100644 --- a/backend/edge/shared/storage/refreshtokens/revoke.go +++ b/backend/product/sessions/store/revoke.go @@ -4,8 +4,8 @@ import ( "context" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/edge/shared/auth/tokens/prepare_test.go b/backend/product/sessions/tokens/prepare_test.go similarity index 84% rename from backend/edge/shared/auth/tokens/prepare_test.go rename to backend/product/sessions/tokens/prepare_test.go index 524778e..d8fe687 100644 --- a/backend/edge/shared/auth/tokens/prepare_test.go +++ b/backend/product/sessions/tokens/prepare_test.go @@ -2,21 +2,22 @@ package tokens import ( "context" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "net/http" "testing" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/product/sessions/store" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) type recordingRefreshTokenDB struct { refreshtokens.DB - token *model.RefreshToken + token *sessionmodel.RefreshToken } -func (db *recordingRefreshTokenDB) Create(_ context.Context, token *model.RefreshToken) error { +func (db *recordingRefreshTokenDB) Create(_ context.Context, token *sessionmodel.RefreshToken) error { db.token = token return nil } @@ -72,7 +73,7 @@ func TestPrepareRefreshTokenStoresResolvedClientIP(t *testing.T) { request.Header.Set("X-Real-IP", tt.realIP) } - account := &model.Account{} + account := &customermodel.Account{} account.SetID(bson.NewObjectID()) db := &recordingRefreshTokenDB{} _, err := PrepareRefreshToken( @@ -81,7 +82,7 @@ func TestPrepareRefreshTokenStoresResolvedClientIP(t *testing.T) { db, 32, 24, - &model.SessionIdentifier{ClientID: "client", DeviceID: "device"}, + &sessionmodel.SessionIdentifier{ClientID: "client", DeviceID: "device"}, account, zap.NewNop(), ) diff --git a/backend/edge/shared/auth/tokens/tokens.go b/backend/product/sessions/tokens/tokens.go similarity index 73% rename from backend/edge/shared/auth/tokens/tokens.go rename to backend/product/sessions/tokens/tokens.go index 9b7037c..fc682eb 100644 --- a/backend/edge/shared/auth/tokens/tokens.go +++ b/backend/product/sessions/tokens/tokens.go @@ -4,16 +4,17 @@ import ( "context" "crypto/rand" "encoding/base64" + customermodel "git.sendico.io/tech/sxchnge/product/customers/model" + sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model" "io" "net/http" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/http/ipguard" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/refreshtokens" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/product/auth/ipguard" + "git.sendico.io/tech/sxchnge/product/sessions/store" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.uber.org/zap" ) @@ -32,10 +33,10 @@ func PrepareRefreshToken( rtdb refreshtokens.DB, length int, refreshExpiration int, - session *model.SessionIdentifier, - account *model.Account, + session *sessionmodel.SessionIdentifier, + account *customermodel.Account, logger mlogger.Logger, -) (*model.RefreshToken, error) { +) (*sessionmodel.RefreshToken, error) { refreshToken, err := generateRefreshTokenData(length) if err != nil { logger.Warn("Failed to generate refresh token", zap.Error(err), mzap.StorableRef(account)) @@ -55,9 +56,9 @@ func PrepareRefreshToken( ipAddress = clientIP.String() } - token := &model.RefreshToken{ + token := &sessionmodel.RefreshToken{ AccountRef: account.GetID(), - ClientRefreshToken: model.ClientRefreshToken{ + ClientRefreshToken: sessionmodel.ClientRefreshToken{ SessionIdentifier: versionedSession, RefreshToken: refreshToken, }, diff --git a/backend/product/verification/config/config.go b/backend/product/verification/config/config.go new file mode 100644 index 0000000..043f635 --- /dev/null +++ b/backend/product/verification/config/config.go @@ -0,0 +1,6 @@ +package config + +type Config struct { + AllowedPurposes []string `yaml:"allowed_purposes"` + RateLimits *VerificationRateLimitsConfig `yaml:"rate_limits"` +} diff --git a/backend/edge/shared/interface/api/verification_rate_limit.go b/backend/product/verification/config/verification_rate_limit.go similarity index 85% rename from backend/edge/shared/interface/api/verification_rate_limit.go rename to backend/product/verification/config/verification_rate_limit.go index f19bf11..7ca7474 100644 --- a/backend/edge/shared/interface/api/verification_rate_limit.go +++ b/backend/product/verification/config/verification_rate_limit.go @@ -1,9 +1,9 @@ -package api +package config import ( "time" - sharedlimit "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/ratelimit" + sharedlimit "git.sendico.io/tech/sxchnge/shared/ratelimit" ) type VerificationRateLimitsConfig struct { @@ -23,7 +23,7 @@ func DefaultVerificationRateLimits() VerificationRateLimits { } } -func (c *VerificationConfig) ResolveRateLimits() (VerificationRateLimits, error) { +func (c *Config) ResolveRateLimits() (VerificationRateLimits, error) { if c == nil || c.RateLimits == nil { return DefaultVerificationRateLimits(), nil } diff --git a/backend/edge/shared/interface/api/verification_rate_limit_test.go b/backend/product/verification/config/verification_rate_limit_test.go similarity index 72% rename from backend/edge/shared/interface/api/verification_rate_limit_test.go rename to backend/product/verification/config/verification_rate_limit_test.go index 1b7180b..4278fc8 100644 --- a/backend/edge/shared/interface/api/verification_rate_limit_test.go +++ b/backend/product/verification/config/verification_rate_limit_test.go @@ -1,16 +1,16 @@ -package api +package config import ( "errors" "testing" "time" - sharedlimit "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/ratelimit" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" + sharedlimit "git.sendico.io/tech/sxchnge/shared/ratelimit" ) func TestVerificationRateLimitsDefaults(t *testing.T) { - got, err := (*VerificationConfig)(nil).ResolveRateLimits() + got, err := (*Config)(nil).ResolveRateLimits() if err != nil { t.Fatalf("resolve defaults: %v", err) } @@ -21,7 +21,7 @@ func TestVerificationRateLimitsDefaults(t *testing.T) { } func TestVerificationRateLimitsRejectInvalidPolicy(t *testing.T) { - cfg := &VerificationConfig{RateLimits: &VerificationRateLimitsConfig{ + cfg := &Config{RateLimits: &VerificationRateLimitsConfig{ PerDestination: sharedlimit.WindowConfig{Limit: 5, Window: "1h"}, PerIP: sharedlimit.WindowConfig{Limit: 0, Window: "1h"}, }} diff --git a/backend/pkg/model/verificaton.go b/backend/product/verification/model/verificaton.go similarity index 95% rename from backend/pkg/model/verificaton.go rename to backend/product/verification/model/verificaton.go index 698e977..388691f 100644 --- a/backend/pkg/model/verificaton.go +++ b/backend/product/verification/model/verificaton.go @@ -4,8 +4,8 @@ import ( "fmt" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/edge/shared/storage/verification/consume.go b/backend/product/verification/store/consume.go similarity index 83% rename from backend/edge/shared/storage/verification/consume.go rename to backend/product/verification/store/consume.go index a87bdcc..e9bf840 100644 --- a/backend/edge/shared/storage/verification/consume.go +++ b/backend/product/verification/store/consume.go @@ -3,9 +3,9 @@ package verification import ( "context" "errors" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) @@ -13,10 +13,10 @@ import ( func (db *verificationDB) Consume( ct context.Context, accountRef bson.ObjectID, - purpose model.VerificationPurpose, + purpose verificationmodel.VerificationPurpose, contextRef string, rawToken string, -) (*model.VerificationToken, error) { +) (*verificationmodel.VerificationToken, error) { scope, err := newConsumeScope(accountRef, purpose, contextRef, rawToken) if err != nil { return nil, err @@ -57,7 +57,7 @@ func (db *verificationDB) finishConsume( scope consumeScope, result any, err error, -) (*model.VerificationToken, error) { +) (*verificationmodel.VerificationToken, error) { if err != nil { db.Logger.Debug("Verification consume finished with error", scope.logFields(zap.Error(err))..., @@ -65,7 +65,7 @@ func (db *verificationDB) finishConsume( return nil, err } - token, ok := result.(*model.VerificationToken) + token, ok := result.(*verificationmodel.VerificationToken) if !ok { db.Logger.Warn("Verification consume returned unexpected result type", scope.logFields(zap.String("result_type", ""))..., diff --git a/backend/edge/shared/storage/verification/consume_logging.go b/backend/product/verification/store/consume_logging.go similarity index 88% rename from backend/edge/shared/storage/verification/consume_logging.go rename to backend/product/verification/store/consume_logging.go index 30c5c96..9acddbc 100644 --- a/backend/edge/shared/storage/verification/consume_logging.go +++ b/backend/product/verification/store/consume_logging.go @@ -1,11 +1,10 @@ package verification import ( - "strings" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.uber.org/zap" + "strings" ) const hashPreviewChars = 12 @@ -32,7 +31,7 @@ func tokenIsDigitsOnly(value string) bool { return true } -func tokenStateFields(token *model.VerificationToken) []zap.Field { +func tokenStateFields(token *verificationmodel.VerificationToken) []zap.Field { fields := []zap.Field{ mzap.StorableRef(token), mzap.ObjRef("token_account_ref", token.AccountRef), diff --git a/backend/edge/shared/storage/verification/consume_lookup.go b/backend/product/verification/store/consume_lookup.go similarity index 87% rename from backend/edge/shared/storage/verification/consume_lookup.go rename to backend/product/verification/store/consume_lookup.go index a6c10c2..0bfa186 100644 --- a/backend/edge/shared/storage/verification/consume_lookup.go +++ b/backend/product/verification/store/consume_lookup.go @@ -3,13 +3,13 @@ package verification import ( "context" "errors" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - mutil "git.sendico.io/tech/sxchnge/bff/pkg/mutil/db" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + mutil "git.sendico.io/tech/sxchnge/shared/database/query" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) @@ -19,7 +19,7 @@ type consumeMissError struct { tokenIDs []bson.ObjectID } -func newConsumeMissError(tokens []model.VerificationToken) consumeMissError { +func newConsumeMissError(tokens []verificationmodel.VerificationToken) consumeMissError { tokenIDs := make([]bson.ObjectID, 0, len(tokens)) for i := range tokens { if tokens[i].ID != bson.NilObjectID { @@ -43,7 +43,7 @@ func (e consumeMissError) Unwrap() error { func (db *verificationDB) lookupConsumeToken( ctx context.Context, scope consumeScope, -) (*model.VerificationToken, error) { +) (*verificationmodel.VerificationToken, error) { scopeFilter := scope.scopeFilter() db.Logger.Debug("Verification consume scope filter prepared", scope.logFields()...) @@ -66,7 +66,7 @@ func (db *verificationDB) lookupConsumeToken( func (db *verificationDB) lookupConsumeMagicToken( ctx context.Context, scope consumeScope, -) (*model.VerificationToken, error) { +) (*verificationmodel.VerificationToken, error) { magicFilter := repository.Query().And( append( scope.scopeFilters(), @@ -75,7 +75,7 @@ func (db *verificationDB) lookupConsumeMagicToken( ) db.Logger.Debug("Verification consume magic path lookup", scope.logFields()...) - var direct model.VerificationToken + var direct verificationmodel.VerificationToken err := db.FindOne(ctx, magicFilter, &direct) switch { case err == nil: @@ -98,11 +98,11 @@ func (db *verificationDB) lookupConsumeOTPToken( ctx context.Context, scope consumeScope, scopeFilter builder.Query, -) (*model.VerificationToken, error) { +) (*verificationmodel.VerificationToken, error) { db.Logger.Debug("Verification consume OTP fallback lookup started", scope.logFields(zap.Any("scope_filter", scopeFilter.BuildQuery()))..., ) - tokens, err := mutil.GetObjects[model.VerificationToken]( + tokens, err := mutil.GetObjects[verificationmodel.VerificationToken]( ctx, db.Logger, scopeFilter, nil, db.Repository, ) if err != nil { diff --git a/backend/edge/shared/storage/verification/consume_scope.go b/backend/product/verification/store/consume_scope.go similarity index 84% rename from backend/edge/shared/storage/verification/consume_scope.go rename to backend/product/verification/store/consume_scope.go index 7481c34..ea4a1f7 100644 --- a/backend/edge/shared/storage/verification/consume_scope.go +++ b/backend/product/verification/store/consume_scope.go @@ -1,12 +1,12 @@ package verification import ( + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "strings" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) @@ -14,7 +14,7 @@ import ( type consumeScope struct { now time.Time accountRef bson.ObjectID - purpose model.VerificationPurpose + purpose verificationmodel.VerificationPurpose contextRef string rawToken string trimmedRawToken string @@ -24,7 +24,7 @@ type consumeScope struct { func newConsumeScope( accountRef bson.ObjectID, - purpose model.VerificationPurpose, + purpose verificationmodel.VerificationPurpose, contextRef string, rawToken string, ) (consumeScope, error) { @@ -60,7 +60,7 @@ func (scope consumeScope) logFields(extra ...zap.Field) []zap.Field { return append(fields, extra...) } -func (scope consumeScope) tokenLogFields(token *model.VerificationToken, extra ...zap.Field) []zap.Field { +func (scope consumeScope) tokenLogFields(token *verificationmodel.VerificationToken, extra ...zap.Field) []zap.Field { fields := scope.logFields(extra...) return append(fields, tokenStateFields(token)...) } @@ -91,7 +91,7 @@ func (scope consumeScope) scopeFilter() builder.Query { return repository.Query().And(scope.scopeFilters()...) } -func (scope consumeScope) consumeFilter(token *model.VerificationToken) builder.Query { +func (scope consumeScope) consumeFilter(token *verificationmodel.VerificationToken) builder.Query { filters := []builder.Query{ repository.IDFilter(token.ID), repository.Filter("purpose", scope.purpose), diff --git a/backend/edge/shared/storage/verification/consume_update.go b/backend/product/verification/store/consume_update.go similarity index 89% rename from backend/edge/shared/storage/verification/consume_update.go rename to backend/product/verification/store/consume_update.go index 488c42b..1f6f2a9 100644 --- a/backend/edge/shared/storage/verification/consume_update.go +++ b/backend/product/verification/store/consume_update.go @@ -2,17 +2,17 @@ package verification import ( "context" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.uber.org/zap" ) func (db *verificationDB) validateConsumeToken( scope consumeScope, - token *model.VerificationToken, + token *verificationmodel.VerificationToken, ) error { if token.UsedAt != nil { db.Logger.Debug("Verification consume static check failed: token already used", @@ -42,8 +42,8 @@ func (db *verificationDB) validateConsumeToken( func (db *verificationDB) consumeMatchedToken( ctx context.Context, scope consumeScope, - token *model.VerificationToken, -) (*model.VerificationToken, error) { + token *verificationmodel.VerificationToken, +) (*verificationmodel.VerificationToken, error) { db.Logger.Debug("Verification consume atomic update attempt", scope.logFields(mzap.StorableRef(token))..., ) @@ -76,7 +76,7 @@ func (db *verificationDB) consumeMatchedToken( func (db *verificationDB) incrementConsumeAttemptsAfterAtomicMiss( ctx context.Context, scope consumeScope, - token *model.VerificationToken, + token *verificationmodel.VerificationToken, ) { incremented, err := db.PatchMany( ctx, @@ -102,9 +102,9 @@ func (db *verificationDB) incrementConsumeAttemptsAfterAtomicMiss( func (db *verificationDB) consumeMissError( ctx context.Context, scope consumeScope, - token *model.VerificationToken, + token *verificationmodel.VerificationToken, ) error { - var fresh model.VerificationToken + var fresh verificationmodel.VerificationToken if err := db.FindOne(ctx, repository.IDFilter(token.ID), &fresh); err != nil { db.Logger.Warn("Verification consume failed to re-check token state", scope.logFields(mzap.StorableRef(token), zap.Error(err))..., diff --git a/backend/product/verification/store/context_ref.go b/backend/product/verification/store/context_ref.go new file mode 100644 index 0000000..088ba48 --- /dev/null +++ b/backend/product/verification/store/context_ref.go @@ -0,0 +1,22 @@ +package verification + +import ( + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" + "strings" + + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" +) + +func normalizeContextRef(purpose verificationmodel.VerificationPurpose, contextRef string) (string, error) { + _ = purpose + return strings.TrimSpace(contextRef), nil +} + +func appendContextRefFilter(filters []builder.Query, purpose verificationmodel.VerificationPurpose, contextRef string) []builder.Query { + _ = purpose + if strings.TrimSpace(contextRef) != "" { + filters = append(filters, repository.Filter("contextRef", strings.TrimSpace(contextRef))) + } + return filters +} diff --git a/backend/edge/shared/storage/verification/context_ref_test.go b/backend/product/verification/store/context_ref_test.go similarity index 78% rename from backend/edge/shared/storage/verification/context_ref_test.go rename to backend/product/verification/store/context_ref_test.go index 25ff1c2..990d2c8 100644 --- a/backend/edge/shared/storage/verification/context_ref_test.go +++ b/backend/product/verification/store/context_ref_test.go @@ -3,13 +3,12 @@ package verification import ( "context" "errors" - "testing" - "time" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" + "testing" + "time" ) func TestCreate_BlankContextRefAllowed(t *testing.T) { @@ -33,11 +32,11 @@ func TestCreate_OldContextDoesNotBlockNewContext(t *testing.T) { require.NoError(t, err) assert.NotEqual(t, oldRaw, newRaw) - oldTok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "ctx-old", oldRaw) + oldTok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "ctx-old", oldRaw) require.NoError(t, err) assert.Equal(t, "ctx-old", oldTok.ContextRef) - newTok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "ctx-new", newRaw) + newTok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "ctx-new", newRaw) require.NoError(t, err) assert.Equal(t, "ctx-new", newTok.ContextRef) } @@ -50,11 +49,11 @@ func TestConsume_OTPForOneContextCannotConfirmAnotherContext(t *testing.T) { raw, err := db.Create(ctx, contextReq(accountRef, "ctx-a")) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "ctx-b", raw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "ctx-b", raw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenNotFound)) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "ctx-a", raw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "ctx-a", raw) require.NoError(t, err) assert.Equal(t, "ctx-a", tok.ContextRef) } @@ -90,10 +89,10 @@ func TestCreate_ContextRefStoredForAnyPurpose(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour).WithContextRef("ctx")) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithContextRef("ctx")) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "ctx", raw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "ctx", raw) require.NoError(t, err) assert.Equal(t, "ctx", tok.ContextRef) } diff --git a/backend/edge/shared/storage/verification/create.go b/backend/product/verification/store/create.go similarity index 92% rename from backend/edge/shared/storage/verification/create.go rename to backend/product/verification/store/create.go index 8a71bc6..61f76ef 100644 --- a/backend/edge/shared/storage/verification/create.go +++ b/backend/product/verification/store/create.go @@ -3,12 +3,12 @@ package verification import ( "context" "errors" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" ) type CreateResult struct { @@ -54,7 +54,7 @@ func newVerificationToken( request *Request, idempotencyKey string, hasIdempotency bool, -) (*model.VerificationToken, string, error) { +) (*verificationmodel.VerificationToken, string, error) { now := storedVerificationInstant(time.Now()) @@ -91,7 +91,7 @@ func newVerificationToken( } } - token := &model.VerificationToken{ + token := &verificationmodel.VerificationToken{ AccountRef: request.AccountRef, Purpose: request.Purpose, Target: request.Target, @@ -164,7 +164,7 @@ func (db *verificationDB) CreateWithResult( return CreateResult{Created: false, ExpiresAt: replayedToken.ExpiresAt}, nil } - var sameToken model.VerificationToken + var sameToken verificationmodel.VerificationToken err = db.FindOne(tx, hashFilter(token.VerifyTokenHash), &sameToken) switch { case err == nil: @@ -180,7 +180,7 @@ func (db *verificationDB) CreateWithResult( if request.Cooldown != nil { cutoff := now.Add(-*request.Cooldown) - var recent model.VerificationToken + var recent verificationmodel.VerificationToken err := db.FindOne(tx, cooldownActiveContextFilter(request, now, cutoff), &recent) switch { case err == nil: @@ -219,7 +219,7 @@ func (db *verificationDB) CreateWithResult( return CreateResult{Created: false, ExpiresAt: replayedToken.ExpiresAt}, nil } - var sameToken model.VerificationToken + var sameToken verificationmodel.VerificationToken findErr := db.FindOne(tx, hashFilter(token.VerifyTokenHash), &sameToken) switch { case findErr == nil: diff --git a/backend/edge/shared/storage/verification/db.go b/backend/product/verification/store/db.go similarity index 77% rename from backend/edge/shared/storage/verification/db.go rename to backend/product/verification/store/db.go index dd9df96..e495739 100644 --- a/backend/edge/shared/storage/verification/db.go +++ b/backend/product/verification/store/db.go @@ -1,21 +1,21 @@ package verification import ( + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "strings" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/ratelimits" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/database/template" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/ratelimit/store" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/mongo" "go.uber.org/zap" ) type verificationDB struct { - template.DBImp[*model.VerificationToken] + template.DBImp[*verificationmodel.VerificationToken] tf transaction.Factory limits ratelimits.Store } @@ -45,7 +45,7 @@ func CreateWithCollection( collection = mservice.VerificationTokens } p := &verificationDB{ - DBImp: *template.Create[*model.VerificationToken](logger, mservice.Type(collection), db), + DBImp: *template.Create[*verificationmodel.VerificationToken](logger, mservice.Type(collection), db), tf: tf, } diff --git a/backend/edge/shared/storage/verification/errors.go b/backend/product/verification/store/errors.go similarity index 97% rename from backend/edge/shared/storage/verification/errors.go rename to backend/product/verification/store/errors.go index f3b7350..b07f8c2 100644 --- a/backend/edge/shared/storage/verification/errors.go +++ b/backend/product/verification/store/errors.go @@ -3,7 +3,7 @@ package verification import ( "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" ) var ( diff --git a/backend/edge/shared/storage/verification/hash.go b/backend/product/verification/store/hash.go similarity index 84% rename from backend/edge/shared/storage/verification/hash.go rename to backend/product/verification/store/hash.go index 1ec163d..2c91556 100644 --- a/backend/edge/shared/storage/verification/hash.go +++ b/backend/product/verification/store/hash.go @@ -6,27 +6,26 @@ import ( "encoding/base64" "encoding/hex" "fmt" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" ) type TokenHasher interface { - Hash(raw string, token *model.VerificationToken) string + Hash(raw string, token *verificationmodel.VerificationToken) string } type magicHasher struct{} -func (magicHasher) Hash(raw string, _ *model.VerificationToken) string { +func (magicHasher) Hash(raw string, _ *verificationmodel.VerificationToken) string { return tokenHash(raw) } type otpHasher struct{} -func (otpHasher) Hash(raw string, t *model.VerificationToken) string { +func (otpHasher) Hash(raw string, t *verificationmodel.VerificationToken) string { return otpHash(raw, *t.Salt) } -func hasherFor(t *model.VerificationToken) TokenHasher { +func hasherFor(t *verificationmodel.VerificationToken) TokenHasher { if t.Salt != nil { return otpHasher{} } diff --git a/backend/edge/shared/storage/verification/hash_test.go b/backend/product/verification/store/hash_test.go similarity index 100% rename from backend/edge/shared/storage/verification/hash_test.go rename to backend/product/verification/store/hash_test.go diff --git a/backend/edge/shared/storage/verification/idempotency.go b/backend/product/verification/store/idempotency.go similarity index 76% rename from backend/edge/shared/storage/verification/idempotency.go rename to backend/product/verification/store/idempotency.go index 82fcaee..22c3852 100644 --- a/backend/edge/shared/storage/verification/idempotency.go +++ b/backend/product/verification/store/idempotency.go @@ -3,13 +3,13 @@ package verification import ( "context" "errors" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "strings" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - mutil "git.sendico.io/tech/sxchnge/bff/pkg/mutil/db" + mutil "git.sendico.io/tech/sxchnge/shared/database/query" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -51,8 +51,8 @@ func idempotencySeed(request *Request, idempotencyKey string) string { }, "|") } -func (db *verificationDB) lookupIdempotencyReplay(ctx context.Context, request *Request, idempotencyKey string) (*model.VerificationToken, bool, error) { - tokens, err := mutil.GetObjects[model.VerificationToken]( +func (db *verificationDB) lookupIdempotencyReplay(ctx context.Context, request *Request, idempotencyKey string) (*verificationmodel.VerificationToken, bool, error) { + tokens, err := mutil.GetObjects[verificationmodel.VerificationToken]( ctx, db.Logger, idempotencyContextFilter(request, idempotencyKey), @@ -67,7 +67,7 @@ func (db *verificationDB) lookupIdempotencyReplay(ctx context.Context, request * return nil, false, err } - var replayed *model.VerificationToken + var replayed *verificationmodel.VerificationToken for i := range tokens { existingContextRef := strings.TrimSpace(tokens[i].ContextRef) // The lookup intentionally ignores contextRef. A reused idempotency key diff --git a/backend/edge/shared/storage/verification/issuance_limit.go b/backend/product/verification/store/issuance_limit.go similarity index 96% rename from backend/edge/shared/storage/verification/issuance_limit.go rename to backend/product/verification/store/issuance_limit.go index 1fa952b..af2367b 100644 --- a/backend/edge/shared/storage/verification/issuance_limit.go +++ b/backend/product/verification/store/issuance_limit.go @@ -5,7 +5,7 @@ import ( "strings" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" ) func (db *verificationDB) reserveIssuance(ctx context.Context, request *Request, now time.Time) error { diff --git a/backend/edge/shared/storage/verification/issuance_limit_test.go b/backend/product/verification/store/issuance_limit_test.go similarity index 96% rename from backend/edge/shared/storage/verification/issuance_limit_test.go rename to backend/product/verification/store/issuance_limit_test.go index a869bac..2123323 100644 --- a/backend/edge/shared/storage/verification/issuance_limit_test.go +++ b/backend/product/verification/store/issuance_limit_test.go @@ -3,13 +3,13 @@ package verification import ( "context" "errors" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "sync" "sync/atomic" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -52,7 +52,7 @@ func TestCreateWithResultDoesNotMutateTokensWhenIssuanceDenied(t *testing.T) { first, err := db.CreateWithResult(context.Background(), NewOTPRequest( accountRef, - model.PurposeLogin, + verificationmodel.PurposeLogin, "user@example.com", ).WithIdempotencyKey("issuance-first").WithIssuanceLimits(limit)) if err != nil || !first.Created { @@ -61,7 +61,7 @@ func TestCreateWithResultDoesNotMutateTokensWhenIssuanceDenied(t *testing.T) { _, err = db.CreateWithResult(context.Background(), NewOTPRequest( accountRef, - model.PurposeLogin, + verificationmodel.PurposeLogin, "user@example.com", ).WithIdempotencyKey("issuance-second").WithIssuanceLimits(limit)) if !errors.Is(err, ErrIssuanceRateLimited) { diff --git a/backend/edge/shared/storage/verification/memory_token_repository_test.go b/backend/product/verification/store/memory_token_repository_test.go similarity index 86% rename from backend/edge/shared/storage/verification/memory_token_repository_test.go rename to backend/product/verification/store/memory_token_repository_test.go index a5afafe..16f41a6 100644 --- a/backend/edge/shared/storage/verification/memory_token_repository_test.go +++ b/backend/product/verification/store/memory_token_repository_test.go @@ -3,42 +3,42 @@ package verification import ( "context" "errors" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "sync" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - rd "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/decoder" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + rd "git.sendico.io/tech/sxchnge/shared/database/repository/decoder" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) type memoryTokenRepository struct { mu sync.Mutex - data map[bson.ObjectID]*model.VerificationToken + data map[bson.ObjectID]*verificationmodel.VerificationToken order []bson.ObjectID seq int } type memoryTokenRepositorySnapshot struct { - data map[bson.ObjectID]*model.VerificationToken + data map[bson.ObjectID]*verificationmodel.VerificationToken order []bson.ObjectID seq int } func newMemoryTokenRepository() *memoryTokenRepository { - return &memoryTokenRepository{data: make(map[bson.ObjectID]*model.VerificationToken)} + return &memoryTokenRepository{data: make(map[bson.ObjectID]*verificationmodel.VerificationToken)} } func (m *memoryTokenRepository) snapshot() memoryTokenRepositorySnapshot { m.mu.Lock() defer m.mu.Unlock() - data := make(map[bson.ObjectID]*model.VerificationToken, len(m.data)) + data := make(map[bson.ObjectID]*verificationmodel.VerificationToken, len(m.data)) for id, tok := range m.data { data[id] = cloneToken(tok) } @@ -53,7 +53,7 @@ func (m *memoryTokenRepository) restore(snapshot memoryTokenRepositorySnapshot) m.mu.Lock() defer m.mu.Unlock() - m.data = make(map[bson.ObjectID]*model.VerificationToken, len(snapshot.data)) + m.data = make(map[bson.ObjectID]*verificationmodel.VerificationToken, len(snapshot.data)) for id, tok := range snapshot.data { m.data[id] = cloneToken(tok) } @@ -64,7 +64,7 @@ func (m *memoryTokenRepository) restore(snapshot memoryTokenRepositorySnapshot) func (m *memoryTokenRepository) Insert(_ context.Context, obj storable.Storable, _ builder.Query) error { m.mu.Lock() defer m.mu.Unlock() - tok, ok := obj.(*model.VerificationToken) + tok, ok := obj.(*verificationmodel.VerificationToken) if !ok { return merrors.InvalidDataType("expected VerificationToken") } @@ -113,7 +113,7 @@ func (m *memoryTokenRepository) Get(_ context.Context, id bson.ObjectID, result if !ok { return merrors.ErrNoData } - dst := result.(*model.VerificationToken) + dst := result.(*verificationmodel.VerificationToken) *dst = *cloneToken(tok) return nil } @@ -124,7 +124,7 @@ func (m *memoryTokenRepository) FindOneByFilter(_ context.Context, query builder for _, id := range m.order { tok := m.data[id] if tok != nil && matchToken(query, tok) { - dst := result.(*model.VerificationToken) + dst := result.(*verificationmodel.VerificationToken) *dst = *cloneToken(tok) return nil } @@ -139,7 +139,7 @@ func (m *memoryTokenRepository) FindFirstByFilter(ctx context.Context, query bui func (m *memoryTokenRepository) Update(_ context.Context, obj storable.Storable) error { m.mu.Lock() defer m.mu.Unlock() - tok := obj.(*model.VerificationToken) + tok := obj.(*verificationmodel.VerificationToken) id := tok.GetID() if id == nil { return merrors.InvalidArgument("id required") @@ -154,7 +154,7 @@ func (m *memoryTokenRepository) Update(_ context.Context, obj storable.Storable) func (m *memoryTokenRepository) ReplaceOneByFilter(_ context.Context, query builder.Query, obj storable.Storable) (bool, error) { m.mu.Lock() defer m.mu.Unlock() - tok, ok := obj.(*model.VerificationToken) + tok, ok := obj.(*verificationmodel.VerificationToken) if !ok { return false, merrors.InvalidDataType("expected VerificationToken") } @@ -212,7 +212,7 @@ func (m *memoryTokenRepository) PatchOneByFilter(_ context.Context, filter build } applyPatch(tok, patchDoc) if result != nil { - dst := result.(*model.VerificationToken) + dst := result.(*verificationmodel.VerificationToken) *dst = *cloneToken(tok) } return true, nil diff --git a/backend/edge/shared/storage/verification/request.go b/backend/product/verification/store/request.go similarity index 79% rename from backend/edge/shared/storage/verification/request.go rename to backend/product/verification/store/request.go index 72fab88..636db6e 100644 --- a/backend/edge/shared/storage/verification/request.go +++ b/backend/product/verification/store/request.go @@ -1,11 +1,11 @@ package verification import ( + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "strings" "time" - "git.sendico.io/tech/sxchnge/bff/edge/shared/storage/ratelimits" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/ratelimit/store" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -37,7 +37,7 @@ const ( type Request struct { AccountRef bson.ObjectID - Purpose model.VerificationPurpose + Purpose verificationmodel.VerificationPurpose Target string ContextRef string Ttl time.Duration @@ -53,7 +53,7 @@ func (r *Request) WithIssuanceLimits(limits ...IssuanceLimit) *Request { return r } -func newRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, kind TokenKind) *Request { +func newRequest(accountRef bson.ObjectID, purpose verificationmodel.VerificationPurpose, target string, kind TokenKind) *Request { return &Request{ AccountRef: accountRef, Purpose: purpose, @@ -63,11 +63,11 @@ func newRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, tar } } -func NewLinkRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string) *Request { +func NewLinkRequest(accountRef bson.ObjectID, purpose verificationmodel.VerificationPurpose, target string) *Request { return newRequest(accountRef, purpose, target, TokenKindLink) } -func NewOTPRequest(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string) *Request { +func NewOTPRequest(accountRef bson.ObjectID, purpose verificationmodel.VerificationPurpose, target string) *Request { return newRequest(accountRef, purpose, target, TokenKindOTP) } diff --git a/backend/edge/shared/storage/verification/token_query_matcher_test.go b/backend/product/verification/store/token_query_matcher_test.go similarity index 84% rename from backend/edge/shared/storage/verification/token_query_matcher_test.go rename to backend/product/verification/store/token_query_matcher_test.go index 5cc511f..e3a8cd2 100644 --- a/backend/edge/shared/storage/verification/token_query_matcher_test.go +++ b/backend/product/verification/store/token_query_matcher_test.go @@ -1,14 +1,14 @@ package verification import ( + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) -func tokenFieldValue(tok *model.VerificationToken, field string) any { +func tokenFieldValue(tok *verificationmodel.VerificationToken, field string) any { switch field { case "_id": return tok.ID @@ -44,14 +44,14 @@ func tokenFieldValue(tok *model.VerificationToken, field string) any { } } -func matchToken(query builder.Query, tok *model.VerificationToken) bool { +func matchToken(query builder.Query, tok *verificationmodel.VerificationToken) bool { if query == nil { return true } return matchBsonD(query.BuildQuery(), tok) } -func matchBsonD(filter bson.D, tok *model.VerificationToken) bool { +func matchBsonD(filter bson.D, tok *verificationmodel.VerificationToken) bool { for _, elem := range filter { if !matchElem(elem, tok) { return false @@ -60,7 +60,7 @@ func matchBsonD(filter bson.D, tok *model.VerificationToken) bool { return true } -func matchElem(elem bson.E, tok *model.VerificationToken) bool { +func matchElem(elem bson.E, tok *verificationmodel.VerificationToken) bool { switch elem.Key { case "$and": arr, ok := elem.Value.(bson.A) @@ -151,8 +151,8 @@ func valuesEqual(a, b any) bool { return av == bv } return false - case model.VerificationPurpose: - if bv, ok := b.(model.VerificationPurpose); ok { + case verificationmodel.VerificationPurpose: + if bv, ok := b.(verificationmodel.VerificationPurpose); ok { return av == bv } return false @@ -202,7 +202,7 @@ func toTime(v any) (time.Time, bool) { return time.Time{}, false } -func applyPatch(tok *model.VerificationToken, patchDoc bson.D) { +func applyPatch(tok *verificationmodel.VerificationToken, patchDoc bson.D) { for _, op := range patchDoc { switch op.Key { case "$set": @@ -235,7 +235,7 @@ func applyPatch(tok *model.VerificationToken, patchDoc bson.D) { } } -func cloneToken(src *model.VerificationToken) *model.VerificationToken { +func cloneToken(src *verificationmodel.VerificationToken) *verificationmodel.VerificationToken { dst := *src if src.UsedAt != nil { t := *src.UsedAt diff --git a/backend/edge/shared/storage/verification/verification.go b/backend/product/verification/store/verification.go similarity index 56% rename from backend/edge/shared/storage/verification/verification.go rename to backend/product/verification/store/verification.go index b893a2c..491ada4 100644 --- a/backend/edge/shared/storage/verification/verification.go +++ b/backend/product/verification/store/verification.go @@ -2,14 +2,13 @@ package verification import ( "context" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "go.mongodb.org/mongo-driver/v2/bson" ) type DB interface { - // template.DB[*model.VerificationToken] + // template.DB[*verificationmodel.VerificationToken] Create(ctx context.Context, request *Request) (verificationToken string, err error) CreateWithResult(ctx context.Context, request *Request) (CreateResult, error) - Consume(ctx context.Context, accountRef bson.ObjectID, purpose model.VerificationPurpose, contextRef, verificationToken string) (*model.VerificationToken, error) + Consume(ctx context.Context, accountRef bson.ObjectID, purpose verificationmodel.VerificationPurpose, contextRef, verificationToken string) (*verificationmodel.VerificationToken, error) } diff --git a/backend/edge/shared/storage/verification/verification_attempts_test.go b/backend/product/verification/store/verification_attempts_test.go similarity index 69% rename from backend/edge/shared/storage/verification/verification_attempts_test.go rename to backend/product/verification/store/verification_attempts_test.go index 1148a54..c97420e 100644 --- a/backend/edge/shared/storage/verification/verification_attempts_test.go +++ b/backend/product/verification/store/verification_attempts_test.go @@ -3,13 +3,12 @@ package verification import ( "context" "errors" - "testing" - "time" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" + "testing" + "time" ) func TestConsume_MaxRetriesExceeded(t *testing.T) { @@ -17,7 +16,7 @@ func TestConsume_MaxRetriesExceeded(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - r := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithMaxRetries(2) + r := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithMaxRetries(2) raw, err := db.Create(ctx, r) require.NoError(t, err) @@ -28,7 +27,7 @@ func TestConsume_MaxRetriesExceeded(t *testing.T) { } repo.mu.Unlock() - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenAttemptsExceeded)) } @@ -38,7 +37,7 @@ func TestConsume_UnderMaxRetriesSucceeds(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - r := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithMaxRetries(3) + r := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithMaxRetries(3) raw, err := db.Create(ctx, r) require.NoError(t, err) @@ -49,7 +48,7 @@ func TestConsume_UnderMaxRetriesSucceeds(t *testing.T) { } repo.mu.Unlock() - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } @@ -59,7 +58,7 @@ func TestConsume_NoMaxRetriesIgnoresAttempts(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) repo := db.Repository.(*memoryTokenRepository) @@ -69,7 +68,7 @@ func TestConsume_NoMaxRetriesIgnoresAttempts(t *testing.T) { } repo.mu.Unlock() - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } @@ -79,10 +78,10 @@ func TestConsume_WrongHashReturnsNotFound(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - _, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + _, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", "wrong-code") + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", "wrong-code") require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenNotFound)) } @@ -94,7 +93,7 @@ func TestConsume_WrongOTPAttemptsPersistAfterTransactionRollback(t *testing.T) { raw, err := db.Create( ctx, - NewOTPRequest(accountRef, model.PurposeEmailChange, "new@example.com"). + NewOTPRequest(accountRef, verificationmodel.PurposeEmailChange, "new@example.com"). WithTTL(time.Hour). WithMaxRetries(2), ) @@ -102,13 +101,13 @@ func TestConsume_WrongOTPAttemptsPersistAfterTransactionRollback(t *testing.T) { wrongCode := differentOTP(raw) for attempt := 1; attempt <= 2; attempt++ { - _, err = db.Consume(ctx, accountRef, model.PurposeEmailChange, "", wrongCode) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposeEmailChange, "", wrongCode) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenNotFound)) assert.Equal(t, attempt, storedTokenAttempts(t, db)) } - _, err = db.Consume(ctx, accountRef, model.PurposeEmailChange, "", raw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposeEmailChange, "", raw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenAttemptsExceeded)) } @@ -119,10 +118,10 @@ func TestConsume_ContextMismatchReturnsNotFound(t *testing.T) { accountRef := bson.NewObjectID() otherAccount := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Consume(ctx, otherAccount, model.PurposePasswordReset, "", raw) + _, err = db.Consume(ctx, otherAccount, verificationmodel.PurposePasswordReset, "", raw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenNotFound)) } diff --git a/backend/edge/shared/storage/verification/verification_idempotency_test.go b/backend/product/verification/store/verification_idempotency_test.go similarity index 66% rename from backend/edge/shared/storage/verification/verification_idempotency_test.go rename to backend/product/verification/store/verification_idempotency_test.go index 5ee9fab..d270a55 100644 --- a/backend/edge/shared/storage/verification/verification_idempotency_test.go +++ b/backend/product/verification/store/verification_idempotency_test.go @@ -3,13 +3,12 @@ package verification import ( "context" "errors" - "testing" - "time" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" + "testing" + "time" ) func TestCreate_CooldownBlocksCreation(t *testing.T) { @@ -17,10 +16,10 @@ func TestCreate_CooldownBlocksCreation(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - _, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + _, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - r2 := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithCooldown(time.Minute) + r2 := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithCooldown(time.Minute) _, err = db.Create(ctx, r2) require.Error(t, err) assert.True(t, errors.Is(err, ErrCooldownActive)) @@ -31,21 +30,21 @@ func TestCreate_CooldownExpiresAllowsCreation(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - firstRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + firstRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) time.Sleep(2 * time.Millisecond) - r2 := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithCooldown(time.Millisecond) + r2 := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithCooldown(time.Millisecond) secondRaw, err := db.Create(ctx, r2) require.NoError(t, err) assert.NotEqual(t, firstRaw, secondRaw) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", firstRaw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", firstRaw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenAlreadyUsed)) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", secondRaw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", secondRaw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } @@ -55,10 +54,10 @@ func TestCreate_CooldownNilIgnored(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - _, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + _, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + _, err = db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) } @@ -67,12 +66,12 @@ func TestCreate_IdempotencyKeyReplayReturnsSameToken(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - firstReq := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithIdempotencyKey("same-key") + firstReq := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithIdempotencyKey("same-key") firstRaw, err := db.Create(ctx, firstReq) require.NoError(t, err) require.NotEmpty(t, firstRaw) - secondReq := req(accountRef, model.PurposePasswordReset, "", time.Hour).WithIdempotencyKey("same-key") + secondReq := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour).WithIdempotencyKey("same-key") secondRaw, err := db.Create(ctx, secondReq) require.NoError(t, err) assert.Equal(t, firstRaw, secondRaw) @@ -88,13 +87,13 @@ func TestCreateWithResult_IdempotencyReplayReportsNotCreated(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - firstReq := req(accountRef, model.PurposeEmailChange, "new@example.com", time.Hour).WithIdempotencyKey("same-key") + firstReq := req(accountRef, verificationmodel.PurposeEmailChange, "new@example.com", time.Hour).WithIdempotencyKey("same-key") first, err := db.CreateWithResult(ctx, firstReq) require.NoError(t, err) require.NotEmpty(t, first.VerificationToken) assert.True(t, first.Created) - secondReq := req(accountRef, model.PurposeEmailChange, "new@example.com", time.Hour).WithIdempotencyKey("same-key") + secondReq := req(accountRef, verificationmodel.PurposeEmailChange, "new@example.com", time.Hour).WithIdempotencyKey("same-key") second, err := db.CreateWithResult(ctx, secondReq) require.NoError(t, err) assert.Equal(t, first.VerificationToken, second.VerificationToken) @@ -111,13 +110,13 @@ func TestCreateWithResult_IdempotencyReplayReusesExpiredStoredExpiry(t *testing. ctx := context.Background() accountRef := bson.NewObjectID() - firstReq := req(accountRef, model.PurposeEmailChange, "new@example.com", -time.Minute).WithIdempotencyKey("same-key") + firstReq := req(accountRef, verificationmodel.PurposeEmailChange, "new@example.com", -time.Minute).WithIdempotencyKey("same-key") first, err := db.CreateWithResult(ctx, firstReq) require.NoError(t, err) require.NotEmpty(t, first.VerificationToken) require.True(t, first.ExpiresAt.Before(time.Now().UTC()), "test must create an already expired token") - secondReq := req(accountRef, model.PurposeEmailChange, "new@example.com", time.Hour).WithIdempotencyKey("same-key") + secondReq := req(accountRef, verificationmodel.PurposeEmailChange, "new@example.com", time.Hour).WithIdempotencyKey("same-key") second, err := db.CreateWithResult(ctx, secondReq) require.NoError(t, err) assert.Equal(t, first.VerificationToken, second.VerificationToken) @@ -131,22 +130,22 @@ func TestCreate_IdempotencyScopeIncludesTarget(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - r1 := req(accountRef, model.PurposeEmailChange, "a@example.com", time.Hour).WithIdempotencyKey("same-key") + r1 := req(accountRef, verificationmodel.PurposeEmailChange, "a@example.com", time.Hour).WithIdempotencyKey("same-key") raw1, err := db.Create(ctx, r1) require.NoError(t, err) require.NotEmpty(t, raw1) - r2 := req(accountRef, model.PurposeEmailChange, "b@example.com", time.Hour).WithIdempotencyKey("same-key") + r2 := req(accountRef, verificationmodel.PurposeEmailChange, "b@example.com", time.Hour).WithIdempotencyKey("same-key") raw2, err := db.Create(ctx, r2) require.NoError(t, err) require.NotEmpty(t, raw2) assert.NotEqual(t, raw1, raw2) - t1, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, "", raw1) + t1, err := db.Consume(ctx, accountRef, verificationmodel.PurposeEmailChange, "", raw1) require.NoError(t, err) assert.Equal(t, "a@example.com", t1.Target) - t2, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, "", raw2) + t2, err := db.Consume(ctx, accountRef, verificationmodel.PurposeEmailChange, "", raw2) require.NoError(t, err) assert.Equal(t, "b@example.com", t2.Target) } @@ -156,7 +155,7 @@ func TestCreate_IdempotencySurvivesCallbackRetry(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - r := req(accountRef, model.PurposePasswordReset, "", time.Hour). + r := req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour). WithCooldown(time.Minute). WithIdempotencyKey("retry-safe") diff --git a/backend/edge/shared/storage/verification/verification_test.go b/backend/product/verification/store/verification_test.go similarity index 58% rename from backend/edge/shared/storage/verification/verification_test.go rename to backend/product/verification/store/verification_test.go index aed6ca7..9f994d1 100644 --- a/backend/edge/shared/storage/verification/verification_test.go +++ b/backend/product/verification/store/verification_test.go @@ -3,14 +3,13 @@ package verification import ( "context" "errors" - "strings" - "testing" - "time" - - "git.sendico.io/tech/sxchnge/bff/pkg/model" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" + "strings" + "testing" + "time" ) func TestCreate_ReturnsRawToken(t *testing.T) { @@ -18,7 +17,7 @@ func TestCreate_ReturnsRawToken(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) assert.NotEmpty(t, raw) } @@ -28,13 +27,13 @@ func TestCreate_TokenCanBeConsumed(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) - assert.Equal(t, model.PurposePasswordReset, tok.Purpose) + assert.Equal(t, verificationmodel.PurposePasswordReset, tok.Purpose) assert.NotNil(t, tok.UsedAt) } @@ -43,13 +42,13 @@ func TestConsume_ReturnsCorrectFields(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposeEmailChange, "new@example.com", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposeEmailChange, "new@example.com", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, "", raw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposeEmailChange, "", raw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) - assert.Equal(t, model.PurposeEmailChange, tok.Purpose) + assert.Equal(t, verificationmodel.PurposeEmailChange, tok.Purpose) assert.Equal(t, "new@example.com", tok.Target) } @@ -58,13 +57,13 @@ func TestConsume_SecondConsumeFailsAlreadyUsed(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenAlreadyUsed), "second consume should fail with already-used after usedAt is set") @@ -76,10 +75,10 @@ func TestConsume_ExpiredTokenFails(t *testing.T) { accountRef := bson.NewObjectID() // Create with a TTL that is already in the past. - raw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", -time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", -time.Hour)) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenExpired), "expired token should return explicit expiry error") @@ -99,13 +98,13 @@ func TestConsume_AccountActivationWithoutAccountRef(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - raw, err := db.Create(ctx, req(accountRef, model.PurposeAccountActivation, "", time.Hour)) + raw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposeAccountActivation, "", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, bson.NilObjectID, model.PurposeAccountActivation, "", raw) + tok, err := db.Consume(ctx, bson.NilObjectID, verificationmodel.PurposeAccountActivation, "", raw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) - assert.Equal(t, model.PurposeAccountActivation, tok.Purpose) + assert.Equal(t, verificationmodel.PurposeAccountActivation, tok.Purpose) } func TestCreate_InvalidatesPreviousToken(t *testing.T) { @@ -113,21 +112,21 @@ func TestCreate_InvalidatesPreviousToken(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - oldRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + oldRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - newRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + newRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) assert.NotEqual(t, oldRaw, newRaw, "new token should differ from old one") // Old token is no longer consumable — invalidated (usedAt set) by the second Create. - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", oldRaw) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", oldRaw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenAlreadyUsed), "old token should return already-used after invalidation") // New token works fine. - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", newRaw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", newRaw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } @@ -138,24 +137,24 @@ func TestCreate_AccountActivationResendWithoutIdempotency_ReissuesToken(t *testi accountRef := bson.NewObjectID() // First issue for the same verification scope. - firstRaw, err := db.Create(ctx, req(accountRef, model.PurposeAccountActivation, "", time.Hour)) + firstRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposeAccountActivation, "", time.Hour)) require.NoError(t, err) // Second issue during resend should rotate token instead of failing with duplicate key. - secondRaw, err := db.Create(ctx, req(accountRef, model.PurposeAccountActivation, "", time.Hour)) + secondRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposeAccountActivation, "", time.Hour)) require.NoError(t, err) assert.NotEqual(t, firstRaw, secondRaw) // Old token becomes unusable after reissue. - _, err = db.Consume(ctx, bson.NilObjectID, model.PurposeAccountActivation, "", firstRaw) + _, err = db.Consume(ctx, bson.NilObjectID, verificationmodel.PurposeAccountActivation, "", firstRaw) require.Error(t, err) assert.True(t, errors.Is(err, ErrTokenAlreadyUsed)) // New token is valid. - tok, err := db.Consume(ctx, bson.NilObjectID, model.PurposeAccountActivation, "", secondRaw) + tok, err := db.Consume(ctx, bson.NilObjectID, verificationmodel.PurposeAccountActivation, "", secondRaw) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) - assert.Equal(t, model.PurposeAccountActivation, tok.Purpose) + assert.Equal(t, verificationmodel.PurposeAccountActivation, tok.Purpose) // Non-idempotent requests should still persist unique internal keys, // preventing uniqueness collisions on (accountRef, purpose, target, idempotencyKey). @@ -165,7 +164,7 @@ func TestCreate_AccountActivationResendWithoutIdempotency_ReissuesToken(t *testi keys := map[string]struct{}{} for _, stored := range repo.data { - if stored.AccountRef != accountRef || stored.Purpose != model.PurposeAccountActivation { + if stored.AccountRef != accountRef || stored.Purpose != verificationmodel.PurposeAccountActivation { continue } require.NotNil(t, stored.IdempotencyKey) @@ -180,19 +179,19 @@ func TestCreate_InvalidatesMultiplePreviousTokens(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - first, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + first, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - second, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + second, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - third, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + third, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", first) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", first) assert.True(t, errors.Is(err, ErrTokenAlreadyUsed), "first should be invalidated/used") - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", second) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", second) assert.True(t, errors.Is(err, ErrTokenAlreadyUsed), "second should be invalidated/used") - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", third) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", third) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } @@ -202,16 +201,16 @@ func TestCreate_DifferentPurposeNotInvalidated(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - resetRaw, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + resetRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) // Creating an activation token should NOT invalidate the password-reset token. - _, err = db.Create(ctx, req(accountRef, model.PurposeAccountActivation, "", time.Hour)) + _, err = db.Create(ctx, req(accountRef, verificationmodel.PurposeAccountActivation, "", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", resetRaw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", resetRaw) require.NoError(t, err) - assert.Equal(t, model.PurposePasswordReset, tok.Purpose) + assert.Equal(t, verificationmodel.PurposePasswordReset, tok.Purpose) } func TestCreate_DifferentTargetNotInvalidated(t *testing.T) { @@ -219,14 +218,14 @@ func TestCreate_DifferentTargetNotInvalidated(t *testing.T) { ctx := context.Background() accountRef := bson.NewObjectID() - firstRaw, err := db.Create(ctx, req(accountRef, model.PurposeEmailChange, "a@example.com", time.Hour)) + firstRaw, err := db.Create(ctx, req(accountRef, verificationmodel.PurposeEmailChange, "a@example.com", time.Hour)) require.NoError(t, err) // Creating a token for a different target email should NOT invalidate the first. - _, err = db.Create(ctx, req(accountRef, model.PurposeEmailChange, "b@example.com", time.Hour)) + _, err = db.Create(ctx, req(accountRef, verificationmodel.PurposeEmailChange, "b@example.com", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposeEmailChange, "", firstRaw) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposeEmailChange, "", firstRaw) require.NoError(t, err) assert.Equal(t, "a@example.com", tok.Target) } @@ -237,13 +236,13 @@ func TestCreate_DifferentAccountNotInvalidated(t *testing.T) { account1 := bson.NewObjectID() account2 := bson.NewObjectID() - raw1, err := db.Create(ctx, req(account1, model.PurposePasswordReset, "", time.Hour)) + raw1, err := db.Create(ctx, req(account1, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Create(ctx, req(account2, model.PurposePasswordReset, "", time.Hour)) + _, err = db.Create(ctx, req(account2, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, account1, model.PurposePasswordReset, "", raw1) + tok, err := db.Consume(ctx, account1, verificationmodel.PurposePasswordReset, "", raw1) require.NoError(t, err) assert.Equal(t, account1, tok.AccountRef) } @@ -254,18 +253,18 @@ func TestCreate_AlreadyUsedTokenNotInvalidatedAgain(t *testing.T) { accountRef := bson.NewObjectID() // Create and consume first token. - raw1, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw1, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - _, err = db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw1) + _, err = db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw1) require.NoError(t, err) // Create second — the already-consumed token should have usedAt set, // so the invalidation query (usedAt == nil) should skip it. // This tests that the PatchMany filter correctly excludes already-used tokens. - raw2, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw2, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw2) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw2) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } @@ -276,14 +275,14 @@ func TestCreate_ExpiredTokenNotInvalidated(t *testing.T) { accountRef := bson.NewObjectID() // Create a token that is already expired. - _, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", -time.Hour)) + _, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", -time.Hour)) require.NoError(t, err) // Create a fresh one — invalidation should skip the expired token (expiresAt > now filter). - raw2, err := db.Create(ctx, req(accountRef, model.PurposePasswordReset, "", time.Hour)) + raw2, err := db.Create(ctx, req(accountRef, verificationmodel.PurposePasswordReset, "", time.Hour)) require.NoError(t, err) - tok, err := db.Consume(ctx, accountRef, model.PurposePasswordReset, "", raw2) + tok, err := db.Consume(ctx, accountRef, verificationmodel.PurposePasswordReset, "", raw2) require.NoError(t, err) assert.Equal(t, accountRef, tok.AccountRef) } diff --git a/backend/edge/shared/storage/verification/verification_test_helpers_test.go b/backend/product/verification/store/verification_test_helpers_test.go similarity index 79% rename from backend/edge/shared/storage/verification/verification_test_helpers_test.go rename to backend/product/verification/store/verification_test_helpers_test.go index 1229061..0a95274 100644 --- a/backend/edge/shared/storage/verification/verification_test_helpers_test.go +++ b/backend/product/verification/store/verification_test_helpers_test.go @@ -2,13 +2,13 @@ package verification import ( "context" + verificationmodel "git.sendico.io/tech/sxchnge/product/verification/model" "sync" "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/template" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/template" + "git.sendico.io/tech/sxchnge/shared/database/transaction" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) @@ -22,7 +22,7 @@ func newRollbackTestVerificationDB(t *testing.T) *verificationDB { repo := newMemoryTokenRepository() logger := zap.NewNop() return &verificationDB{ - DBImp: template.DBImp[*model.VerificationToken]{ + DBImp: template.DBImp[*verificationmodel.VerificationToken]{ Logger: logger, Repository: repo, }, @@ -35,7 +35,7 @@ func newTestVerificationDBWithFactory(t *testing.T, tf transaction.Factory) *ver repo := newMemoryTokenRepository() logger := zap.NewNop() return &verificationDB{ - DBImp: template.DBImp[*model.VerificationToken]{ + DBImp: template.DBImp[*verificationmodel.VerificationToken]{ Logger: logger, Repository: repo, }, @@ -92,12 +92,12 @@ func (tx *rollbackTx) Execute(ctx context.Context, cb transaction.Callback) (any return result, nil } -func req(accountRef bson.ObjectID, purpose model.VerificationPurpose, target string, ttl time.Duration) *Request { +func req(accountRef bson.ObjectID, purpose verificationmodel.VerificationPurpose, target string, ttl time.Duration) *Request { return NewLinkRequest(accountRef, purpose, target).WithTTL(ttl) } func contextReq(accountRef bson.ObjectID, contextRef string) *Request { - return NewOTPRequest(accountRef, model.PurposePasswordReset, "user@example.com"). + return NewOTPRequest(accountRef, verificationmodel.PurposePasswordReset, "user@example.com"). WithTTL(time.Hour). WithContextRef(contextRef) } diff --git a/backend/shared/README.md b/backend/shared/README.md new file mode 100644 index 0000000..1b26124 --- /dev/null +++ b/backend/shared/README.md @@ -0,0 +1,12 @@ +# Shared backend library + +This is a Go library module, not an executable. It provides database connections, +transactions and repository primitives, errors, structured logging, generic HTTP +responses, configuration and rate-limit helpers. It contains no customer, session, +verification or Sendico domain models and imports no executable SXCHNGE module. + +`product` consumes it through a local module replacement. A future backoffice +executable can consume the same infrastructure independently. + +Run `go test -race ./...` and `go vet ./...` here, or use the root Make targets to +validate all implemented backend modules. diff --git a/backend/pkg/mutil/config/param.go b/backend/shared/config/param.go similarity index 97% rename from backend/pkg/mutil/config/param.go rename to backend/shared/config/param.go index abb2b10..250ea53 100644 --- a/backend/pkg/mutil/config/param.go +++ b/backend/shared/config/param.go @@ -4,7 +4,7 @@ import ( "os" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/logging" "go.uber.org/zap" ) diff --git a/backend/shared/database/config.go b/backend/shared/database/config.go new file mode 100644 index 0000000..9d04665 --- /dev/null +++ b/backend/shared/database/config.go @@ -0,0 +1,13 @@ +package db + +import ( + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" +) + +type DBDriver string + +const ( + Mongo DBDriver = "mongodb" +) + +type Config = sharedmodel.DriverConfig[DBDriver] diff --git a/backend/pkg/db/connection.go b/backend/shared/database/connection.go similarity index 89% rename from backend/pkg/db/connection.go rename to backend/shared/database/connection.go index 10cc975..52092fd 100644 --- a/backend/pkg/db/connection.go +++ b/backend/shared/database/connection.go @@ -3,9 +3,9 @@ package db import ( "context" - mongoimpl "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + mongoimpl "git.sendico.io/tech/sxchnge/shared/database/internal/mongo" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/readpref" ) diff --git a/backend/edge/shared/storage/mongo/dependencies.go b/backend/shared/database/dependencies/dependencies.go similarity index 84% rename from backend/edge/shared/storage/mongo/dependencies.go rename to backend/shared/database/dependencies/dependencies.go index 0b22592..e8c4dc2 100644 --- a/backend/edge/shared/storage/mongo/dependencies.go +++ b/backend/shared/database/dependencies/dependencies.go @@ -1,9 +1,9 @@ package mongo import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/db/factory.go b/backend/shared/database/factory.go similarity index 56% rename from backend/pkg/db/factory.go rename to backend/shared/database/factory.go index 5a49298..592d81b 100644 --- a/backend/pkg/db/factory.go +++ b/backend/shared/database/factory.go @@ -1,18 +1,14 @@ package db import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - mongoimpl "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + mongoimpl "git.sendico.io/tech/sxchnge/shared/database/internal/mongo" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" ) -// Factory exposes high-level repositories used by application services. +// Factory exposes the local connection and transaction lifecycle. type Factory interface { - NewAccountDB() (account.DB, error) - NewAccountDBWithCollection(collection string) (account.DB, error) - TransactionFactory() transaction.Factory CloseConnection() diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/accumulator.go b/backend/shared/database/internal/builder/accumulator.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/accumulator.go rename to backend/shared/database/internal/builder/accumulator.go index c59e89c..c465306 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/accumulator.go +++ b/backend/shared/database/internal/builder/accumulator.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/alias.go b/backend/shared/database/internal/builder/alias.go similarity index 96% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/alias.go rename to backend/shared/database/internal/builder/alias.go index bf9c508..7773b3e 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/alias.go +++ b/backend/shared/database/internal/builder/alias.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/array.go b/backend/shared/database/internal/builder/array.go similarity index 89% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/array.go rename to backend/shared/database/internal/builder/array.go index 5c0a4ad..2a3fd0b 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/array.go +++ b/backend/shared/database/internal/builder/array.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/expression.go b/backend/shared/database/internal/builder/expression.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/expression.go rename to backend/shared/database/internal/builder/expression.go index 719ac97..b1b5397 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/expression.go +++ b/backend/shared/database/internal/builder/expression.go @@ -3,7 +3,7 @@ package builderimp import ( "reflect" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/field.go b/backend/shared/database/internal/builder/field.go similarity index 95% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/field.go rename to backend/shared/database/internal/builder/field.go index e53a22c..f19bcd0 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/field.go +++ b/backend/shared/database/internal/builder/field.go @@ -3,7 +3,7 @@ package builderimp import ( "strings" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" ) type FieldImp struct { diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/func.go b/backend/shared/database/internal/builder/func.go similarity index 98% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/func.go rename to backend/shared/database/internal/builder/func.go index 18ed6e0..718bd00 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/func.go +++ b/backend/shared/database/internal/builder/func.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/gaccumulator.go b/backend/shared/database/internal/builder/gaccumulator.go similarity index 92% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/gaccumulator.go rename to backend/shared/database/internal/builder/gaccumulator.go index 4ee062b..8a52d68 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/gaccumulator.go +++ b/backend/shared/database/internal/builder/gaccumulator.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/patch.go b/backend/shared/database/internal/builder/patch.go similarity index 96% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/patch.go rename to backend/shared/database/internal/builder/patch.go index 9311116..a997bbb 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/patch.go +++ b/backend/shared/database/internal/builder/patch.go @@ -3,8 +3,8 @@ package builderimp import ( "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/patch_test.go b/backend/shared/database/internal/builder/patch_test.go similarity index 95% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/patch_test.go rename to backend/shared/database/internal/builder/patch_test.go index 191badf..b06153e 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/patch_test.go +++ b/backend/shared/database/internal/builder/patch_test.go @@ -4,8 +4,8 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline.go b/backend/shared/database/internal/builder/pipeline.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline.go rename to backend/shared/database/internal/builder/pipeline.go index 245821f..7304b29 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline.go +++ b/backend/shared/database/internal/builder/pipeline.go @@ -1,8 +1,8 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go b/backend/shared/database/internal/builder/pipeline_test.go similarity index 99% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go rename to backend/shared/database/internal/builder/pipeline_test.go index 7592b59..3f49233 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/pipeline_test.go +++ b/backend/shared/database/internal/builder/pipeline_test.go @@ -3,8 +3,8 @@ package builderimp import ( "testing" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/projection.go b/backend/shared/database/internal/builder/projection.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/projection.go rename to backend/shared/database/internal/builder/projection.go index f982533..2de698d 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/projection.go +++ b/backend/shared/database/internal/builder/projection.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/query.go b/backend/shared/database/internal/builder/query.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/query.go rename to backend/shared/database/internal/builder/query.go index fd2ed01..439b300 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/query.go +++ b/backend/shared/database/internal/builder/query.go @@ -3,8 +3,8 @@ package builderimp import ( "reflect" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo/options" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/query_test.go b/backend/shared/database/internal/builder/query_test.go similarity index 92% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/query_test.go rename to backend/shared/database/internal/builder/query_test.go index 934fffb..9db6668 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/query_test.go +++ b/backend/shared/database/internal/builder/query_test.go @@ -3,7 +3,7 @@ package builderimp import ( "testing" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/value.go b/backend/shared/database/internal/builder/value.go similarity index 74% rename from backend/pkg/db/internal/mongo/repositoryimp/builderimp/value.go rename to backend/shared/database/internal/builder/value.go index 26cf000..01fcd50 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/builderimp/value.go +++ b/backend/shared/database/internal/builder/value.go @@ -1,7 +1,7 @@ package builderimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" ) type valueImp struct { diff --git a/backend/pkg/db/internal/mongo/db.go b/backend/shared/database/internal/mongo/db.go similarity index 88% rename from backend/pkg/db/internal/mongo/db.go rename to backend/shared/database/internal/mongo/db.go index a536c5a..1cbd093 100644 --- a/backend/pkg/db/internal/mongo/db.go +++ b/backend/shared/database/internal/mongo/db.go @@ -3,17 +3,15 @@ package mongo import ( "context" "fmt" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" "net" "os" "strings" - "git.sendico.io/tech/sxchnge/bff/pkg/db/account" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/accountdb" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/transactionimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" - mutil "git.sendico.io/tech/sxchnge/bff/pkg/mutil/config" + mutil "git.sendico.io/tech/sxchnge/shared/config" + "git.sendico.io/tech/sxchnge/shared/database/internal/transaction" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/go-viper/mapstructure/v2" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/readpref" @@ -85,7 +83,7 @@ func Config2DBSettings(logger mlogger.Logger, config *Config) *DBSettings { return p } -func decodeConfig(logger mlogger.Logger, settings model.SettingsT) (*Config, *DBSettings, error) { +func decodeConfig(logger mlogger.Logger, settings sharedmodel.SettingsT) (*Config, *DBSettings, error) { var config Config if err := mapstructure.Decode(settings, &config); err != nil { logger.Warn("Failed to decode settings", zap.Error(err)) @@ -121,7 +119,7 @@ func dialMongo(logger mlogger.Logger, dbSettings *DBSettings) (*mongo.Client, er return client, nil } -func ConnectClient(logger mlogger.Logger, settings model.SettingsT) (*mongo.Client, *Config, *DBSettings, error) { +func ConnectClient(logger mlogger.Logger, settings sharedmodel.SettingsT) (*mongo.Client, *Config, *DBSettings, error) { config, dbSettings, err := decodeConfig(logger, settings) if err != nil { return nil, nil, nil, err @@ -150,14 +148,6 @@ func (db *DB) MongoDatabase() *mongo.Database { return db.db() } -func (db *DB) NewAccountDB() (account.DB, error) { - return accountdb.Create(db.logger, db.db()) -} - -func (db *DB) NewAccountDBWithCollection(collection string) (account.DB, error) { - return accountdb.CreateWithCollection(db.logger, db.db(), collection) -} - func (db *DB) TransactionFactory() transaction.Factory { return transactionimp.CreateFactory(db.client) } @@ -234,7 +224,7 @@ func (db *DB) CloseConnection() { } // NewConnection creates a new database connection -func NewConnection(logger mlogger.Logger, settings model.SettingsT) (*DB, error) { +func NewConnection(logger mlogger.Logger, settings sharedmodel.SettingsT) (*DB, error) { client, _, dbSettings, err := ConnectClient(logger, settings) if err != nil { return nil, err diff --git a/backend/pkg/db/internal/mongo/mongo.go b/backend/shared/database/internal/mongo/mongo.go similarity index 100% rename from backend/pkg/db/internal/mongo/mongo.go rename to backend/shared/database/internal/mongo/mongo.go diff --git a/backend/pkg/db/internal/mongo/repositoryimp/index.go b/backend/shared/database/internal/repository/index.go similarity index 96% rename from backend/pkg/db/internal/mongo/repositoryimp/index.go rename to backend/shared/database/internal/repository/index.go index 5d71b17..0c4a39c 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/index.go +++ b/backend/shared/database/internal/repository/index.go @@ -4,8 +4,8 @@ import ( "context" "fmt" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/index_integration_test.go b/backend/shared/database/internal/repository/index_integration_test.go similarity index 95% rename from backend/pkg/db/internal/mongo/repositoryimp/index_integration_test.go rename to backend/shared/database/internal/repository/index_integration_test.go index 2aa6189..b7d5533 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/index_integration_test.go +++ b/backend/shared/database/internal/repository/index_integration_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" + "git.sendico.io/tech/sxchnge/shared/database/repository" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/index_test.go b/backend/shared/database/internal/repository/index_test.go similarity index 95% rename from backend/pkg/db/internal/mongo/repositoryimp/index_test.go rename to backend/shared/database/internal/repository/index_test.go index 94f27d5..2dda698 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/index_test.go +++ b/backend/shared/database/internal/repository/index_test.go @@ -3,7 +3,7 @@ package repositoryimp import ( "testing" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository.go b/backend/shared/database/internal/repository/repository.go similarity index 95% rename from backend/pkg/db/internal/mongo/repositoryimp/repository.go rename to backend/shared/database/internal/repository/repository.go index 18d5eec..f11e249 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository.go +++ b/backend/shared/database/internal/repository/repository.go @@ -5,11 +5,11 @@ import ( "errors" "fmt" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - rd "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/decoder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + rd "git.sendico.io/tech/sxchnge/shared/database/repository/decoder" + "git.sendico.io/tech/sxchnge/shared/database/transaction" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository_comprehensive_test.go b/backend/shared/database/internal/repository/repository_comprehensive_test.go similarity index 98% rename from backend/pkg/db/internal/mongo/repositoryimp/repository_comprehensive_test.go rename to backend/shared/database/internal/repository/repository_comprehensive_test.go index 4adf08d..cd38f92 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository_comprehensive_test.go +++ b/backend/shared/database/internal/repository/repository_comprehensive_test.go @@ -9,10 +9,10 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp/builderimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database/internal/builder" + "git.sendico.io/tech/sxchnge/shared/database/internal/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository_filter.go b/backend/shared/database/internal/repository/repository_filter.go similarity index 90% rename from backend/pkg/db/internal/mongo/repositoryimp/repository_filter.go rename to backend/shared/database/internal/repository/repository_filter.go index 186ee24..381bb1e 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository_filter.go +++ b/backend/shared/database/internal/repository/repository_filter.go @@ -4,9 +4,9 @@ import ( "context" "errors" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" ) diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository_insertmany_test.go b/backend/shared/database/internal/repository/repository_insertmany_test.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/repository_insertmany_test.go rename to backend/shared/database/internal/repository/repository_insertmany_test.go index 68f5286..a5f84fa 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository_insertmany_test.go +++ b/backend/shared/database/internal/repository/repository_insertmany_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" + "git.sendico.io/tech/sxchnge/shared/database/internal/repository" + "git.sendico.io/tech/sxchnge/shared/database/record" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository_patch_test.go b/backend/shared/database/internal/repository/repository_patch_test.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/repository_patch_test.go rename to backend/shared/database/internal/repository/repository_patch_test.go index 1b0e501..be65ee2 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository_patch_test.go +++ b/backend/shared/database/internal/repository/repository_patch_test.go @@ -9,10 +9,10 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database/internal/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository_test.go b/backend/shared/database/internal/repository/repository_test.go similarity index 94% rename from backend/pkg/db/internal/mongo/repositoryimp/repository_test.go rename to backend/shared/database/internal/repository/repository_test.go index ad1b2c6..85655ca 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository_test.go +++ b/backend/shared/database/internal/repository/repository_test.go @@ -9,11 +9,11 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp/builderimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/database/internal/builder" + "git.sendico.io/tech/sxchnge/shared/database/internal/repository" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" diff --git a/backend/pkg/db/internal/mongo/repositoryimp/repository_upsert_test.go b/backend/shared/database/internal/repository/repository_upsert_test.go similarity index 97% rename from backend/pkg/db/internal/mongo/repositoryimp/repository_upsert_test.go rename to backend/shared/database/internal/repository/repository_upsert_test.go index bf8136d..bea26d9 100644 --- a/backend/pkg/db/internal/mongo/repositoryimp/repository_upsert_test.go +++ b/backend/shared/database/internal/repository/repository_upsert_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp" + "git.sendico.io/tech/sxchnge/shared/database/internal/repository" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/testcontainers/testcontainers-go" diff --git a/backend/pkg/db/internal/mongo/transactionimp/factory.go b/backend/shared/database/internal/transaction/factory.go similarity index 86% rename from backend/pkg/db/internal/mongo/transactionimp/factory.go rename to backend/shared/database/internal/transaction/factory.go index 8106414..344b312 100644 --- a/backend/pkg/db/internal/mongo/transactionimp/factory.go +++ b/backend/shared/database/internal/transaction/factory.go @@ -1,7 +1,7 @@ package transactionimp import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" + "git.sendico.io/tech/sxchnge/shared/database/transaction" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/db/internal/mongo/transactionimp/transaction.go b/backend/shared/database/internal/transaction/transaction.go similarity index 92% rename from backend/pkg/db/internal/mongo/transactionimp/transaction.go rename to backend/shared/database/internal/transaction/transaction.go index 73d09f6..458d6c3 100644 --- a/backend/pkg/db/internal/mongo/transactionimp/transaction.go +++ b/backend/shared/database/internal/transaction/transaction.go @@ -3,7 +3,7 @@ package transactionimp import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" + "git.sendico.io/tech/sxchnge/shared/database/transaction" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/mutil/db/archive.go b/backend/shared/database/query/archive.go similarity index 81% rename from backend/pkg/mutil/db/archive.go rename to backend/shared/database/query/archive.go index daf901f..966a1ec 100644 --- a/backend/pkg/mutil/db/archive.go +++ b/backend/shared/database/query/archive.go @@ -3,10 +3,10 @@ package mutil import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/logging/fields" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) diff --git a/backend/pkg/mutil/db/array.go b/backend/shared/database/query/array.go similarity index 60% rename from backend/pkg/mutil/db/array.go rename to backend/shared/database/query/array.go index 7d63abc..208e444 100644 --- a/backend/pkg/mutil/db/array.go +++ b/backend/shared/database/query/array.go @@ -2,16 +2,16 @@ package mutil import ( "context" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/logging" "go.mongodb.org/mongo-driver/v2/mongo" "go.uber.org/zap" ) -func GetObjects[T any](ctx context.Context, logger mlogger.Logger, filter builder.Query, cursor *model.ViewCursor, repo repository.Repository) ([]T, error) { +func GetObjects[T any](ctx context.Context, logger mlogger.Logger, filter builder.Query, cursor *sharedmodel.ViewCursor, repo repository.Repository) ([]T, error) { entities := make([]T, 0) decoder := func(cur *mongo.Cursor) error { var next T diff --git a/backend/pkg/mutil/db/db.go b/backend/shared/database/query/db.go similarity index 74% rename from backend/pkg/mutil/db/db.go rename to backend/shared/database/query/db.go index 646313b..a916622 100644 --- a/backend/pkg/mutil/db/db.go +++ b/backend/shared/database/query/db.go @@ -3,9 +3,9 @@ package mutil import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/logging" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) diff --git a/backend/pkg/db/storable/id.go b/backend/shared/database/record/id.go similarity index 100% rename from backend/pkg/db/storable/id.go rename to backend/shared/database/record/id.go diff --git a/backend/pkg/db/storable/ref.go b/backend/shared/database/record/ref.go similarity index 100% rename from backend/pkg/db/storable/ref.go rename to backend/shared/database/record/ref.go diff --git a/backend/pkg/db/storable/storable.go b/backend/shared/database/record/storable.go similarity index 100% rename from backend/pkg/db/storable/storable.go rename to backend/shared/database/record/storable.go diff --git a/backend/pkg/db/repository/builder/accumulator.go b/backend/shared/database/repository/builder/accumulator.go similarity index 100% rename from backend/pkg/db/repository/builder/accumulator.go rename to backend/shared/database/repository/builder/accumulator.go diff --git a/backend/pkg/db/repository/builder/alias.go b/backend/shared/database/repository/builder/alias.go similarity index 100% rename from backend/pkg/db/repository/builder/alias.go rename to backend/shared/database/repository/builder/alias.go diff --git a/backend/pkg/db/repository/builder/array.go b/backend/shared/database/repository/builder/array.go similarity index 100% rename from backend/pkg/db/repository/builder/array.go rename to backend/shared/database/repository/builder/array.go diff --git a/backend/pkg/db/repository/builder/expression.go b/backend/shared/database/repository/builder/expression.go similarity index 100% rename from backend/pkg/db/repository/builder/expression.go rename to backend/shared/database/repository/builder/expression.go diff --git a/backend/pkg/db/repository/builder/field.go b/backend/shared/database/repository/builder/field.go similarity index 100% rename from backend/pkg/db/repository/builder/field.go rename to backend/shared/database/repository/builder/field.go diff --git a/backend/pkg/db/repository/builder/keyword.go b/backend/shared/database/repository/builder/keyword.go similarity index 100% rename from backend/pkg/db/repository/builder/keyword.go rename to backend/shared/database/repository/builder/keyword.go diff --git a/backend/pkg/db/repository/builder/operators.go b/backend/shared/database/repository/builder/operators.go similarity index 100% rename from backend/pkg/db/repository/builder/operators.go rename to backend/shared/database/repository/builder/operators.go diff --git a/backend/pkg/db/repository/builder/patch.go b/backend/shared/database/repository/builder/patch.go similarity index 100% rename from backend/pkg/db/repository/builder/patch.go rename to backend/shared/database/repository/builder/patch.go diff --git a/backend/pkg/db/repository/builder/pipeline.go b/backend/shared/database/repository/builder/pipeline.go similarity index 93% rename from backend/pkg/db/repository/builder/pipeline.go rename to backend/shared/database/repository/builder/pipeline.go index 957b863..61f36f8 100644 --- a/backend/pkg/db/repository/builder/pipeline.go +++ b/backend/shared/database/repository/builder/pipeline.go @@ -1,7 +1,7 @@ package builder import ( - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/db/repository/builder/projection.go b/backend/shared/database/repository/builder/projection.go similarity index 100% rename from backend/pkg/db/repository/builder/projection.go rename to backend/shared/database/repository/builder/projection.go diff --git a/backend/pkg/db/repository/builder/query.go b/backend/shared/database/repository/builder/query.go similarity index 100% rename from backend/pkg/db/repository/builder/query.go rename to backend/shared/database/repository/builder/query.go diff --git a/backend/pkg/db/repository/builder/unwind.go b/backend/shared/database/repository/builder/unwind.go similarity index 100% rename from backend/pkg/db/repository/builder/unwind.go rename to backend/shared/database/repository/builder/unwind.go diff --git a/backend/pkg/db/repository/builder/value.go b/backend/shared/database/repository/builder/value.go similarity index 100% rename from backend/pkg/db/repository/builder/value.go rename to backend/shared/database/repository/builder/value.go diff --git a/backend/pkg/db/repository/builders.go b/backend/shared/database/repository/builders.go similarity index 94% rename from backend/pkg/db/repository/builders.go rename to backend/shared/database/repository/builders.go index ccf15ec..3eb8ebd 100644 --- a/backend/pkg/db/repository/builders.go +++ b/backend/shared/database/repository/builders.go @@ -1,10 +1,10 @@ package repository import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp/builderimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/internal/builder" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" "go.mongodb.org/mongo-driver/v2/bson" ) @@ -41,11 +41,11 @@ func IDField() builder.Field { } func NameField() builder.Field { - return Field(model.NameField) + return Field(sharedmodel.NameField) } func DescrtiptionField() builder.Field { - return Field(model.DescriptionField) + return Field(sharedmodel.DescriptionField) } func IsArchivedField() builder.Field { @@ -69,7 +69,7 @@ func IsArchivedFilter(isArchived bool) builder.Query { } func AccountField() builder.Field { - return Field(model.AccountRefField) + return Field("accountRef") } func AccountFilter(ref bson.ObjectID) builder.Query { diff --git a/backend/pkg/db/repository/cursor.go b/backend/shared/database/repository/cursor.go similarity index 57% rename from backend/pkg/db/repository/cursor.go rename to backend/shared/database/repository/cursor.go index 8a66b50..49fecc8 100644 --- a/backend/pkg/db/repository/cursor.go +++ b/backend/shared/database/repository/cursor.go @@ -1,12 +1,12 @@ package repository import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/model" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + sharedmodel "git.sendico.io/tech/sxchnge/shared/model" ) // ApplyCursor adds pagination and archival filters to the provided query. -func ApplyCursor(query builder.Query, cursor *model.ViewCursor) builder.Query { +func ApplyCursor(query builder.Query, cursor *sharedmodel.ViewCursor) builder.Query { if cursor == nil { return query } diff --git a/backend/pkg/db/repository/decoder/decoder.go b/backend/shared/database/repository/decoder/decoder.go similarity index 100% rename from backend/pkg/db/repository/decoder/decoder.go rename to backend/shared/database/repository/decoder/decoder.go diff --git a/backend/pkg/db/repository/index/index.go b/backend/shared/database/repository/index/index.go similarity index 89% rename from backend/pkg/db/repository/index/index.go rename to backend/shared/database/repository/index/index.go index 0606bea..d2ccdfd 100644 --- a/backend/pkg/db/repository/index/index.go +++ b/backend/shared/database/repository/index/index.go @@ -1,6 +1,6 @@ package repository -import "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" +import "git.sendico.io/tech/sxchnge/shared/database/repository/builder" type Sort int8 diff --git a/backend/pkg/db/repository/index/types.go b/backend/shared/database/repository/index/types.go similarity index 100% rename from backend/pkg/db/repository/index/types.go rename to backend/shared/database/repository/index/types.go diff --git a/backend/pkg/db/repository/repository.go b/backend/shared/database/repository/repository.go similarity index 88% rename from backend/pkg/db/repository/repository.go rename to backend/shared/database/repository/repository.go index 00bab1d..f53bc95 100644 --- a/backend/pkg/db/repository/repository.go +++ b/backend/shared/database/repository/repository.go @@ -3,12 +3,12 @@ package repository import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db/internal/mongo/repositoryimp" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - rd "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/decoder" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/db/transaction" + "git.sendico.io/tech/sxchnge/shared/database/internal/repository" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + rd "git.sendico.io/tech/sxchnge/shared/database/repository/decoder" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/database/transaction" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/db/template/interface.go b/backend/shared/database/template/interface.go similarity index 84% rename from backend/pkg/db/template/interface.go rename to backend/shared/database/template/interface.go index 61b2556..e8918bd 100644 --- a/backend/pkg/db/template/interface.go +++ b/backend/shared/database/template/interface.go @@ -3,8 +3,8 @@ package template import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" "go.mongodb.org/mongo-driver/v2/bson" ) diff --git a/backend/pkg/db/template/template.go b/backend/shared/database/template/template.go similarity index 87% rename from backend/pkg/db/template/template.go rename to backend/shared/database/template/template.go index e1ad219..93b02cc 100644 --- a/backend/pkg/db/template/template.go +++ b/backend/shared/database/template/template.go @@ -3,13 +3,13 @@ package template import ( "context" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/builder" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mzap" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository" + "git.sendico.io/tech/sxchnge/shared/database/repository/builder" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/logging/fields" + "git.sendico.io/tech/sxchnge/shared/service" "go.mongodb.org/mongo-driver/v2/bson" "go.mongodb.org/mongo-driver/v2/mongo" ) diff --git a/backend/pkg/db/transaction/context.go b/backend/shared/database/transaction/context.go similarity index 100% rename from backend/pkg/db/transaction/context.go rename to backend/shared/database/transaction/context.go diff --git a/backend/pkg/db/transaction/context_test.go b/backend/shared/database/transaction/context_test.go similarity index 100% rename from backend/pkg/db/transaction/context_test.go rename to backend/shared/database/transaction/context_test.go diff --git a/backend/pkg/db/transaction/factory.go b/backend/shared/database/transaction/factory.go similarity index 100% rename from backend/pkg/db/transaction/factory.go rename to backend/shared/database/transaction/factory.go diff --git a/backend/pkg/db/transaction/transaction.go b/backend/shared/database/transaction/transaction.go similarity index 100% rename from backend/pkg/db/transaction/transaction.go rename to backend/shared/database/transaction/transaction.go diff --git a/backend/pkg/mutil/duration/duration.go b/backend/shared/duration/duration.go similarity index 100% rename from backend/pkg/mutil/duration/duration.go rename to backend/shared/duration/duration.go diff --git a/backend/pkg/merrors/classified.go b/backend/shared/errors/classified.go similarity index 100% rename from backend/pkg/merrors/classified.go rename to backend/shared/errors/classified.go diff --git a/backend/pkg/merrors/errors.go b/backend/shared/errors/errors.go similarity index 100% rename from backend/pkg/merrors/errors.go rename to backend/shared/errors/errors.go diff --git a/backend/pkg/merrors/errors_test.go b/backend/shared/errors/errors_test.go similarity index 100% rename from backend/pkg/merrors/errors_test.go rename to backend/shared/errors/errors_test.go diff --git a/backend/pkg/merrors/invalid_argument.go b/backend/shared/errors/invalid_argument.go similarity index 100% rename from backend/pkg/merrors/invalid_argument.go rename to backend/shared/errors/invalid_argument.go diff --git a/backend/pkg/merrors/reason.go b/backend/shared/errors/reason.go similarity index 100% rename from backend/pkg/merrors/reason.go rename to backend/shared/errors/reason.go diff --git a/backend/pkg/merrors/throttle.go b/backend/shared/errors/throttle.go similarity index 100% rename from backend/pkg/merrors/throttle.go rename to backend/shared/errors/throttle.go diff --git a/backend/pkg/merrors/throttle_test.go b/backend/shared/errors/throttle_test.go similarity index 100% rename from backend/pkg/merrors/throttle_test.go rename to backend/shared/errors/throttle_test.go diff --git a/backend/shared/go.mod b/backend/shared/go.mod new file mode 100644 index 0000000..04a90c1 --- /dev/null +++ b/backend/shared/go.mod @@ -0,0 +1,71 @@ +module git.sendico.io/tech/sxchnge/shared + +go 1.27.0 + +require ( + github.com/go-viper/mapstructure/v2 v2.5.0 + github.com/mattn/go-colorable v0.1.15 + github.com/stretchr/testify v1.12.1 + github.com/testcontainers/testcontainers-go v0.44.0 + github.com/testcontainers/testcontainers-go/modules/mongodb v0.44.0 + go.mongodb.org/mongo-driver/v2 v2.9.0 + go.uber.org/zap v1.28.0 +) + +require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.10.1 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/klauspost/compress v1.19.2 // indirect + github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.55.0 // indirect + github.com/moby/moby/client v0.5.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/sequential v0.7.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/shirou/gopsutil/v4 v4.26.6 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/tklauser/go-sysconf v0.4.0 // indirect + github.com/tklauser/numcpus v0.12.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.2.0 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/crypto v0.56.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect +) diff --git a/backend/shared/go.sum b/backend/shared/go.sum new file mode 100644 index 0000000..c0b4aec --- /dev/null +++ b/backend/shared/go.sum @@ -0,0 +1,177 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= +github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +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/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8= +github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak= +github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +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/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= +github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= +github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= +github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/shirou/gopsutil/v4 v4.26.6 h1:Mzr/npDtQC/xpeEuQKHZt8Zo9CmPvhTj8nkR8w5TLDs= +github.com/shirou/gopsutil/v4 v4.26.6/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= +github.com/testcontainers/testcontainers-go v0.44.0 h1:/Fwh6HY1mIikhnm9e7HwoxGycx0lzRAE0f5VQpjFxzI= +github.com/testcontainers/testcontainers-go v0.44.0/go.mod h1:IcnwQrYTO86xHXu5bvMaBH7ATlbS3Qn1M1QWW3c66rE= +github.com/testcontainers/testcontainers-go/modules/mongodb v0.44.0 h1:VSPDFiumAtt0CkZEVbmAkEmYVRvsJpKJy9oF3exRKYg= +github.com/testcontainers/testcontainers-go/modules/mongodb v0.44.0/go.mod h1:kHfzrY1cYP/zr9H4TdqAxbP836A1C2fyUojlHidhFGI= +github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU= +github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI= +github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4= +github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.mongodb.org/mongo-driver/v2 v2.9.0 h1:e2mQdOmbkiYz+dj3faM7lVDwl7WdnRD+g5VicafMhL0= +go.mongodb.org/mongo-driver/v2 v2.9.0/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= +go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/backend/pkg/api/http/methods.go b/backend/shared/http/methods.go similarity index 100% rename from backend/pkg/api/http/methods.go rename to backend/shared/http/methods.go diff --git a/backend/pkg/api/http/response/response.go b/backend/shared/http/response/response.go similarity index 97% rename from backend/pkg/api/http/response/response.go rename to backend/shared/http/response/response.go index 01a331b..e373154 100644 --- a/backend/pkg/api/http/response/response.go +++ b/backend/shared/http/response/response.go @@ -6,10 +6,10 @@ import ( "fmt" "net/http" - api "git.sendico.io/tech/sxchnge/bff/pkg/api/http" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/errors" + api "git.sendico.io/tech/sxchnge/shared/http" + "git.sendico.io/tech/sxchnge/shared/logging" + "git.sendico.io/tech/sxchnge/shared/service" "go.uber.org/zap" ) diff --git a/backend/pkg/api/http/response/response_test.go b/backend/shared/http/response/response_test.go similarity index 92% rename from backend/pkg/api/http/response/response_test.go rename to backend/shared/http/response/response_test.go index b71e996..14c0f96 100644 --- a/backend/pkg/api/http/response/response_test.go +++ b/backend/shared/http/response/response_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mservice" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/service" "github.com/stretchr/testify/require" "go.uber.org/zap" ) diff --git a/backend/pkg/api/http/response/result.go b/backend/shared/http/response/result.go similarity index 86% rename from backend/pkg/api/http/response/result.go rename to backend/shared/http/response/result.go index 0e13b93..6c1f521 100644 --- a/backend/pkg/api/http/response/result.go +++ b/backend/shared/http/response/result.go @@ -3,7 +3,7 @@ package response import ( "net/http" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/logging" ) type Result struct { diff --git a/backend/pkg/api/http/status.go b/backend/shared/http/status.go similarity index 100% rename from backend/pkg/api/http/status.go rename to backend/shared/http/status.go diff --git a/backend/shared/logging/factory/mlogger.go b/backend/shared/logging/factory/mlogger.go new file mode 100644 index 0000000..e051ef4 --- /dev/null +++ b/backend/shared/logging/factory/mlogger.go @@ -0,0 +1,10 @@ +package mlogger + +import ( + lt "git.sendico.io/tech/sxchnge/shared/logging" + li "git.sendico.io/tech/sxchnge/shared/logging/internal" +) + +func NewLogger(debug bool) lt.Logger { + return li.NewLoggerImp(debug) +} diff --git a/backend/pkg/mutil/mzap/account.go b/backend/shared/logging/fields/account.go similarity index 72% rename from backend/pkg/mutil/mzap/account.go rename to backend/shared/logging/fields/account.go index 036179a..b7de948 100644 --- a/backend/pkg/mutil/mzap/account.go +++ b/backend/shared/logging/fields/account.go @@ -1,8 +1,7 @@ package mzap import ( - "git.sendico.io/tech/sxchnge/bff/pkg/model" - "git.sendico.io/tech/sxchnge/bff/pkg/mutil/mask" + "git.sendico.io/tech/sxchnge/shared/logging/mask" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) @@ -22,7 +21,3 @@ func MaskEmail(field, email string) zap.Field { func MaskEmails(field string, emails []string) zap.Field { return zap.Strings(field, mask.Emails(emails)) } - -func Login(account *model.Account) zap.Field { - return Email(account.Login) -} diff --git a/backend/pkg/mutil/mzap/error.go b/backend/shared/logging/fields/error.go similarity index 100% rename from backend/pkg/mutil/mzap/error.go rename to backend/shared/logging/fields/error.go diff --git a/backend/pkg/mutil/mzap/object.go b/backend/shared/logging/fields/object.go similarity index 84% rename from backend/pkg/mutil/mzap/object.go rename to backend/shared/logging/fields/object.go index 01b71fd..e69a83f 100644 --- a/backend/pkg/mutil/mzap/object.go +++ b/backend/shared/logging/fields/object.go @@ -1,7 +1,7 @@ package mzap import ( - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" + "git.sendico.io/tech/sxchnge/shared/database/record" "go.mongodb.org/mongo-driver/v2/bson" "go.uber.org/zap" ) diff --git a/backend/pkg/mlogger/internal/mlogger/mlogger.go b/backend/shared/logging/internal/mlogger.go similarity index 90% rename from backend/pkg/mlogger/internal/mlogger/mlogger.go rename to backend/shared/logging/internal/mlogger.go index 2c0f1cb..db89e78 100644 --- a/backend/pkg/mlogger/internal/mlogger/mlogger.go +++ b/backend/shared/logging/internal/mlogger.go @@ -1,7 +1,7 @@ package mlogger import ( - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/logging" "github.com/mattn/go-colorable" "go.uber.org/zap" "go.uber.org/zap/zapcore" diff --git a/backend/pkg/mlogger/logger.go b/backend/shared/logging/logger.go similarity index 100% rename from backend/pkg/mlogger/logger.go rename to backend/shared/logging/logger.go diff --git a/backend/pkg/mlogger/logger_test.go b/backend/shared/logging/logger_test.go similarity index 100% rename from backend/pkg/mlogger/logger_test.go rename to backend/shared/logging/logger_test.go diff --git a/backend/pkg/mutil/mask/mask.go b/backend/shared/logging/mask/mask.go similarity index 100% rename from backend/pkg/mutil/mask/mask.go rename to backend/shared/logging/mask/mask.go diff --git a/backend/pkg/mutil/mask/mask_test.go b/backend/shared/logging/mask/mask_test.go similarity index 100% rename from backend/pkg/mutil/mask/mask_test.go rename to backend/shared/logging/mask/mask_test.go diff --git a/backend/pkg/model/archivable.go b/backend/shared/model/archivable.go similarity index 100% rename from backend/pkg/model/archivable.go rename to backend/shared/model/archivable.go diff --git a/backend/pkg/model/describable.go b/backend/shared/model/describable.go similarity index 100% rename from backend/pkg/model/describable.go rename to backend/shared/model/describable.go diff --git a/backend/pkg/model/fconfig.go b/backend/shared/model/fconfig.go similarity index 100% rename from backend/pkg/model/fconfig.go rename to backend/shared/model/fconfig.go diff --git a/backend/pkg/model/viewcursor.go b/backend/shared/model/viewcursor.go similarity index 100% rename from backend/pkg/model/viewcursor.go rename to backend/shared/model/viewcursor.go diff --git a/backend/edge/shared/interface/ratelimit/config.go b/backend/shared/ratelimit/config.go similarity index 95% rename from backend/edge/shared/interface/ratelimit/config.go rename to backend/shared/ratelimit/config.go index f76045f..f4d8c28 100644 --- a/backend/edge/shared/interface/ratelimit/config.go +++ b/backend/shared/ratelimit/config.go @@ -4,7 +4,7 @@ import ( "strings" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" + "git.sendico.io/tech/sxchnge/shared/errors" ) const ( diff --git a/backend/edge/shared/storage/ratelimits/db.go b/backend/shared/ratelimit/store/db.go similarity index 94% rename from backend/edge/shared/storage/ratelimits/db.go rename to backend/shared/ratelimit/store/db.go index cbc9995..7069e54 100644 --- a/backend/edge/shared/storage/ratelimits/db.go +++ b/backend/shared/ratelimit/store/db.go @@ -8,11 +8,11 @@ import ( "strings" "time" - "git.sendico.io/tech/sxchnge/bff/pkg/db/repository" - ri "git.sendico.io/tech/sxchnge/bff/pkg/db/repository/index" - "git.sendico.io/tech/sxchnge/bff/pkg/db/storable" - "git.sendico.io/tech/sxchnge/bff/pkg/merrors" - "git.sendico.io/tech/sxchnge/bff/pkg/mlogger" + "git.sendico.io/tech/sxchnge/shared/database/record" + "git.sendico.io/tech/sxchnge/shared/database/repository" + ri "git.sendico.io/tech/sxchnge/shared/database/repository/index" + "git.sendico.io/tech/sxchnge/shared/errors" + "git.sendico.io/tech/sxchnge/shared/logging" "go.mongodb.org/mongo-driver/v2/mongo" "go.uber.org/zap" ) diff --git a/backend/pkg/mservice/mservice.go b/backend/shared/service/mservice.go similarity index 100% rename from backend/pkg/mservice/mservice.go rename to backend/shared/service/mservice.go diff --git a/backend/pkg/mservice/services.go b/backend/shared/service/services.go similarity index 100% rename from backend/pkg/mservice/services.go rename to backend/shared/service/services.go diff --git a/backend/shared/version/factory/factory.go b/backend/shared/version/factory/factory.go new file mode 100644 index 0000000..58f71d0 --- /dev/null +++ b/backend/shared/version/factory/factory.go @@ -0,0 +1,10 @@ +package version + +import ( + "git.sendico.io/tech/sxchnge/shared/version" + versionimp "git.sendico.io/tech/sxchnge/shared/version/internal" +) + +func Create(info *version.Info) version.Printer { + return versionimp.Create(info) +} diff --git a/backend/pkg/version/info.go b/backend/shared/version/info.go similarity index 100% rename from backend/pkg/version/info.go rename to backend/shared/version/info.go diff --git a/backend/pkg/version/internal/version.go b/backend/shared/version/internal/version.go similarity index 98% rename from backend/pkg/version/internal/version.go rename to backend/shared/version/internal/version.go index dd159d6..add4cd5 100644 --- a/backend/pkg/version/internal/version.go +++ b/backend/shared/version/internal/version.go @@ -8,7 +8,7 @@ import ( "strings" "text/template" - "git.sendico.io/tech/sxchnge/bff/pkg/version" + "git.sendico.io/tech/sxchnge/shared/version" ) // versionInfoTmpl contains the template used by Info. diff --git a/backend/pkg/version/version.go b/backend/shared/version/version.go similarity index 100% rename from backend/pkg/version/version.go rename to backend/shared/version/version.go diff --git a/docs/architecture/bff.md b/docs/architecture/bff.md index a4dcd73..c3e1032 100644 --- a/docs/architecture/bff.md +++ b/docs/architecture/bff.md @@ -15,26 +15,30 @@ and allowed to diverge within this repository. Source: `S_I/sendico`, commit `8bb20dcd622dd8eb92d289b6b34411e7eccab083`. The source checkout's unrelated dependency edits were not copied. -| Local component | Reused implementation | -| --- | --- | -| `backend/edge/shared` | Chi HTTP runtime, middleware, JWT login/pending sessions, refresh/rotation/logout, CSRF/cookies, rate limiting, Mongo verification and refresh-token stores | -| `backend/edge/product/interface/accountservice` | Account validation, bcrypt passwords, atomic customer creation, activation tokens, password reset and email-change token issuance, deletion | -| `backend/pkg` | Canonical local account/domain models, Mongo repositories/transactions, errors, structured logging, process bootstrap and health | -| `backend/internal/profile` | Small SXCHNGE composition layer selecting the copied local account service | -| `backend/contracts/sendico` | Separate snapshot of Sendico's public OpenAPI contract and its referenced schemas | +`backend/` groups Go modules. The implemented executable is `backend/product`, +with its own `main.go` and `go.mod`. `backend/shared` is a separate library module. +Backoffice will be a separate executable module (`backend/backoffice`) with its +own entry point, operator identity, configuration and deployment. Product's entry +point is not the entry point for every future backend. -Source provenance and the bounded adaptations are in -[PROVENANCE.md](../../backend/PROVENANCE.md). There is no sibling source import, -private Sendico module requirement, gRPC client, discovery subscription or broker -connection. Platform engines and Product's platform-provisioning bootstrap were -not copied. The original handwritten shell and tiny replacement helpers were removed. +| Owner | Retained implementation | +| --- | --- | +| `product/app` | HTTP runtime, startup, composition and health | +| `product/customers` | Local customer models, service and account repository | +| `product/auth` | Login/protected routes, local DTOs/mappers and auth configuration | +| `product/sessions` | Session/client models, refresh storage, cookies and token generation | +| `product/verification` | Verification models, configuration and storage | +| `shared` | Generic database/transaction/query code, errors, logging and HTTP helpers | + +Shared infrastructure imports no executable module. Customer repositories are +constructed by the product module rather than the generic database factory. +There is no global customer/session model package or Sendico `edge/product` +source hierarchy. Source provenance follows the moved files across Go modules. Local customers have no organization membership, roles, policies or tenant ID. -The account service validates and hashes passwords, creates an account and -activation token atomically, and deletes an account with session revocation. -Customer responses map to explicit local DTOs. Account, token and session collections and application client records belong only -to SXCHNGE. The runtime starts without any policy or tenant seed. Casbin, -invitations, financial models and provisioning machinery are excluded. +Creation validates/hashes passwords and commits the account and activation token +atomically. Deletion revokes that customer's sessions. The runtime starts without +any seed. Casbin, invitations and platform provisioning are absent. ## Identity and ownership @@ -57,9 +61,11 @@ Confirm the deployed credential policy before configuring a provider client. ## Contract and service structure -The copied OpenAPI files are a pinned upstream wire contract, not SXCHNGE's app -API or domain model. Preserve them as an isolated snapshot. Changes require an -explicit contract update; do not follow Sendico source updates automatically. +The bootstrap has no Sendico runtime calls and therefore no copied public wire +snapshot. When implementing an adapter, keep only its consumed request/response +contracts within that adapter. Pin their upstream revision separately from local +customer DTOs/models; do not copy the entire platform contract or synchronize +source automatically. When wiring a feature, keep the existing architecture: @@ -73,13 +79,13 @@ or replace local account services with Sendico login/customer calls. The next adapter will use a configured public HTTPS base URL, a fixed organization reference, bounded request timeouts and an injected HTTP transport. It must reject -cross-origin credential redirects. Reuse the existing public contract and response -conventions; do not invent a parallel provider facade or call internal services. +cross-origin credential redirects. Use the public contract and response +conventions for each implemented operation; do not invent a parallel provider facade or call internal services. ## Required public Sendico capabilities -The snapshot is API version 4.2.37. Source routes and schemas establish available -shapes; they do not prove that a deployment enables every feature. +The source reviewed at the pinned revision describes API version 4.2.37. Its +public routes and schemas establish available shapes; they do not prove that a deployment enables every feature. | Need | Existing public surface / work required | | --- | --- | @@ -120,7 +126,8 @@ operation details explain what has happened and what remains pending. ## Current delivery boundary -This task supplies the copied starting point and isolated public contracts. +This task supplies the selected copied starting point in independently buildable +Go modules and a public API integration plan. The local runtime installs the existing authentication transport and account service. Local signup/profile HTTP endpoints, verification delivery/completion, application client setup, Flutter HTTP adapters and live Sendico calls diff --git a/docs/implementation.md b/docs/implementation.md index 031efe5..040d884 100644 --- a/docs/implementation.md +++ b/docs/implementation.md @@ -379,7 +379,8 @@ Retained Sendico source is pinned to `8bb20dcd622dd8eb92d289b6b34411e7eccab083`: HTTP runtime, local customer/password service and Mongo repository, customer sessions and verification storage, generic repository/transaction code, errors, logging and health. [Provenance](../backend/PROVENANCE.json) covers 232 retained -Go files and 112 isolated public OpenAPI files; every adaptation is flagged. +Go files at that cleanup stage; every adaptation is flagged. The later module +reorganization below removes the unused public OpenAPI snapshot. The Sendico checkout remains unchanged. Customer creation accepts a raw password, validates/hashes it and commits the @@ -416,3 +417,32 @@ not been deployed. Existing experimental databases may retain unused tenancy collections; this change does not delete external database records. Customer HTTP responses now expose only ID, name, last name, login, locale and optional avatar. The frontend still uses dummy services and is unchanged. + +### Go module organization correction + +`backend/` now groups two implemented Go modules: the runnable `product` BFF +(`main.go`, `go.mod`, configuration and feature packages) and the `shared` library +(its own `go.mod`, generic storage, errors, logging and HTTP helpers). Product uses +`replace git.sendico.io/tech/sxchnge/shared => ../shared`. A future backoffice BFF +belongs in its own `backend/backoffice` executable module; it is not implemented +or silently combined with product in this task. + +Customer, session and verification models/storage now live with their features. +Shared database code no longer knows about customer repositories. Auth and +verification configurations belong to their feature packages. The inherited +`cmd`, `edge`, `internal`, `pkg`, `scripts` and `contracts` top-level folders are +removed. All 112 unused contract files are removed; future HTTP adapters will +own only consumed public wire definitions. Provenance retains the pinned upstream +source paths while recording the new local module paths. + +Root Make targets validate both modules independently and run product with +`cd backend/product && go run .`. Source behavior remains the previously validated +local customer/auth/session bootstrap; frontend and Sendico source are untouched. + +Validation passed after the module moves: `make test` (both Go modules with the +race detector and all frontend suites), `make lint-backend`, product executable +build and `-version` smoke check, and `go test -tags integration -run '^$' ./...` +in each Go module (compile-only for the additional tagged suites). All 233 +provenance source blobs match the pinned revision. `git diff --check` passed. +No security scans were requested or run. The correction is delivered on `bff-34` +in [PR #35](https://git.sendico.io/tech/sxchnge/pulls/35), which remains unmerged.