48 Commits

Author SHA1 Message Date
b4b9507e7e Merge pull request 'added code generation before testing' (#712) from cicd-709 into main
All checks were successful
ci/woodpecker/push/frontend Pipeline was successful
Reviewed-on: #712
2026-03-11 11:04:22 +00:00
Stephan D
6911757e1d added code generation before testing 2026-03-11 12:03:56 +01:00
fba992f898 Merge pull request 'fixed frontend tests' (#711) from cicd-709 into main
Some checks failed
ci/woodpecker/push/frontend Pipeline failed
Reviewed-on: #711
2026-03-11 11:00:57 +00:00
Stephan D
5a8392a6d0 fixed frontend tests 2026-03-11 12:00:11 +01:00
6b82825494 Merge pull request 'fixed frontend tests' (#710) from cicd-709 into main
Some checks failed
ci/woodpecker/push/frontend Pipeline failed
Reviewed-on: #710
2026-03-11 10:57:27 +00:00
Stephan D
54e9821886 fixed frontend tests 2026-03-11 11:56:02 +01:00
c74444ab0b Merge pull request 'extended aurora scenarios + payment operation amounts' (#708) from po-707 into main
Some checks failed
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/frontend Pipeline failed
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful
Reviewed-on: #708
2026-03-11 00:34:23 +00:00
aba63ecd37 Merge pull request 'infra updates' (#702) from SEND068 into main
Some checks failed
ci/woodpecker/push/frontend Pipeline failed
Reviewed-on: #702
Reviewed-by: tech <tech.sendico@proton.me>
2026-03-11 00:34:13 +00:00
Stephan D
9ad2104d7d extended aurora scenarios + payment operation amounts 2026-03-11 01:09:11 +01:00
e446486b77 Merge pull request 'cicd-705' (#706) from cicd-705 into main
All checks were successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful
Reviewed-on: #706
2026-03-10 18:51:00 +00:00
Stephan D
65b04fba39 removed buildx setting 2026-03-10 19:50:34 +01:00
Stephan D
bbdbd9a197 amd64 image binding 2026-03-10 19:49:29 +01:00
Arseni
049b23516a added missing files 2026-03-10 21:45:41 +03:00
3862fa4e52 Merge pull request '+source +destination in payments' (#704) from bff-703 into main
Some checks failed
ci/woodpecker/push/bff Pipeline failed
ci/woodpecker/push/frontend Pipeline failed
Reviewed-on: #704
2026-03-10 18:32:38 +00:00
Arseni
7ae4518926 config for vault 2026-03-10 21:26:32 +03:00
Stephan D
e5b4de5d48 +source +destination in payments 2026-03-10 19:15:20 +01:00
Arseni
0e64ab5558 deleted unnecessary changes for this branch 2026-03-10 20:48:24 +03:00
Arseni
840a7f85c8 updated for infra 2026-03-10 20:40:20 +03:00
9c2b3bf8bd Merge pull request 'fixed linting step to CG0 enabled = false' (#701) from cicd-700 into main
Some checks failed
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/payments_methods Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/payments_quotation Pipeline is pending
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline failed
ci/woodpecker/push/ledger Pipeline failed
ci/woodpecker/push/gateway_tgsettle Pipeline failed
Reviewed-on: #701
2026-03-10 17:26:55 +00:00
Stephan D
41720a6817 fixed linting step to CG0 enabled = false 2026-03-10 18:25:21 +01:00
Stephan D
918dbe8bb5 linting added to CI + bypass tags
Some checks failed
ci/woodpecker/push/fx_oracle Pipeline is pending
ci/woodpecker/push/gateway_mntx Pipeline is pending
ci/woodpecker/push/gateway_tron Pipeline is pending
ci/woodpecker/push/ledger Pipeline is pending
ci/woodpecker/push/payments_methods Pipeline is pending
ci/woodpecker/push/payments_orchestrator Pipeline is pending
ci/woodpecker/push/payments_quotation Pipeline is pending
ci/woodpecker/push/gateway_chain Pipeline is pending
ci/woodpecker/push/gateway_tgsettle Pipeline is pending
ci/woodpecker/push/notification Pipeline is pending
ci/woodpecker/push/billing_documents Pipeline failed
ci/woodpecker/push/callbacks Pipeline failed
ci/woodpecker/push/discovery Pipeline failed
ci/woodpecker/push/bff Pipeline failed
ci/woodpecker/push/frontend Pipeline failed
ci/woodpecker/push/billing_fees Pipeline failed
ci/woodpecker/push/fx_ingestor Pipeline failed
2026-03-10 12:39:30 +01:00
Stephan D
6cc0340ba3 version bump 2026-03-10 12:33:07 +01:00
Stephan D
e77d1ab793 linting 2026-03-10 12:31:09 +01:00
d87e709f43 Merge pull request 'removed kaniko caching' (#698) from cicd-697 into main
Reviewed-on: #698
2026-03-06 18:05:42 +00:00
Stephan D
185f8f2ed6 removed kaniko caching 2026-03-06 19:04:36 +01:00
2160b6bf4d Merge pull request 'Billing docs improvement + build opt' (#696) from docs-693 into main
All checks were successful
ci/woodpecker/push/billing_documents Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/callbacks Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_methods Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/payments_quotation Pipeline was successful
Reviewed-on: #696
2026-03-06 15:20:31 +00:00
Stephan D
54bbe41f7a Billing docs improvement + build opt 2026-03-06 16:20:01 +01:00
6633a1d807 Merge pull request 'gw <-> po contracts tests' (#692) from gw-691 into main
All checks were successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_tron Pipeline was successful
Reviewed-on: #692
2026-03-06 14:45:46 +00:00
Stephan D
88b279dd78 gw <-> po contracts tests 2026-03-06 15:45:14 +01:00
0f42a0e77f Merge pull request 'Chimera Settle service' (#690) from chsettle-689 into main
Reviewed-on: #690
2026-03-06 14:43:02 +00:00
Stephan D
10bcdb4fe2 Chimera Settle service 2026-03-06 15:42:32 +01:00
ea5ec79a6e Merge pull request 'fixed po <-> tgsettle contract' (#688) from po-687 into main
All checks were successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
Reviewed-on: #688
2026-03-06 14:12:46 +00:00
Stephan D
3295b9d9f0 fixed po <-> tgsettle contract 2026-03-06 15:12:14 +01:00
031b8931ca Merge pull request 'fixed tgsettle upsert logic' (#686) from tg-685 into main
All checks were successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
Reviewed-on: #686
2026-03-06 12:50:56 +00:00
Stephan D
4295456f63 fixed tgsettle upsert logic 2026-03-06 13:50:13 +01:00
2b1b4135f4 Merge pull request 'mntx error codes update' (#684) from mntx-683 into main
All checks were successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
Reviewed-on: #684
2026-03-06 12:35:09 +00:00
Stephan D
c60e7d2329 mntx error codes update 2026-03-06 12:14:32 +01:00
be49254769 Merge pull request 'bff USDT ledger creation' (#682) from bff-681 into main
All checks were successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
Reviewed-on: #682
2026-03-06 10:58:27 +00:00
Stephan D
34e507b664 bff USDT ledger creation 2026-03-06 11:58:07 +01:00
b481de9ffc Merge pull request 'New comments section in the requests/responses' (#679) from bff-677 into main
Reviewed-on: #679
2026-03-05 19:29:10 +00:00
Stephan D
0c29e7686d New comments section in the requests/responses 2026-03-05 20:28:28 +01:00
5b26a70a15 Merge pull request 'New comments section in the requests/responses' (#678) from bff-677 into main
All checks were successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
Reviewed-on: #678
2026-03-05 19:28:05 +00:00
Stephan D
b832c2a7c4 New comments section in the requests/responses 2026-03-05 20:27:45 +01:00
15393765b9 Merge pull request 'fixes for multiple payout' (#674) from SEND067 into main
All checks were successful
ci/woodpecker/push/frontend Pipeline was successful
Reviewed-on: #674
2026-03-05 16:35:37 +00:00
Arseni
440b6a2553 fixes for multiple payout 2026-03-05 19:28:02 +03:00
bc76cfe063 Merge pull request 'tg-670' (#671) from tg-670 into main
All checks were successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
Reviewed-on: #671
2026-03-05 13:47:37 +00:00
Stephan D
ed8f7c519c moved tg settings to db 2026-03-05 14:46:34 +01:00
Stephan D
71d99338f2 moved tg settings to db 2026-03-05 14:46:26 +01:00
543 changed files with 17220 additions and 3095 deletions

View File

@@ -1,2 +1,18 @@
ci/dev/mongo.key* ci/dev/mongo.key*
# VCS / editor files
.git
.vscode
.DS_Store
**/.DS_Store
# Local caches and temporary artifacts
.cache
.gocache
**/.gocache
**/tmp/
**/tmp/**
# Frontend local build artifacts (rebuilt in Docker)
frontend/**/.dart_tool
frontend/**/build

2
.gitignore vendored
View File

@@ -10,7 +10,9 @@ generate_protos.sh
update_dep.sh update_dep.sh
test.sh test.sh
.vscode/ .vscode/
.gocache/ .gocache/
.golangci-cache/
.cache/ .cache/
.claude/ .claude/

View File

@@ -7,6 +7,9 @@ matrix:
BFF_VAULT_SECRET_PATH: sendico/edge/bff/vault BFF_VAULT_SECRET_PATH: sendico/edge/bff/vault
BFF_ENV: prod BFF_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -47,6 +50,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh bff
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -75,8 +88,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/bff/build-image.sh - sh ci/scripts/bff/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
DOCUMENTS_MONGO_SECRET_PATH: sendico/db DOCUMENTS_MONGO_SECRET_PATH: sendico/db
DOCUMENTS_ENV: prod DOCUMENTS_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -42,6 +45,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh billing_documents
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -70,8 +83,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/billing_documents/build-image.sh - sh ci/scripts/billing_documents/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
FEES_MONGO_SECRET_PATH: sendico/db FEES_MONGO_SECRET_PATH: sendico/db
FEES_ENV: prod FEES_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -42,6 +45,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh billing_fees
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -70,8 +83,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/billing_fees/build-image.sh - sh ci/scripts/billing_fees/build-image.sh
- name: deploy - name: deploy

View File

@@ -6,6 +6,9 @@ matrix:
CALLBACKS_VAULT_SECRET_PATH: sendico/edge/callbacks/vault CALLBACKS_VAULT_SECRET_PATH: sendico/edge/callbacks/vault
CALLBACKS_ENV: prod CALLBACKS_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -43,6 +46,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh callbacks
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -71,8 +84,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/callbacks/build-image.sh - sh ci/scripts/callbacks/build-image.sh
- name: deploy - name: deploy

View File

@@ -4,6 +4,9 @@ matrix:
DISCOVERY_DOCKERFILE: ci/prod/compose/discovery.dockerfile DISCOVERY_DOCKERFILE: ci/prod/compose/discovery.dockerfile
DISCOVERY_ENV: prod DISCOVERY_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -41,6 +44,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh discovery
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -69,8 +82,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/discovery/build-image.sh - sh ci/scripts/discovery/build-image.sh
- name: deploy - name: deploy

View File

@@ -4,6 +4,9 @@ matrix:
FRONTEND_DOCKERFILE: ci/prod/compose/frontend.dockerfile FRONTEND_DOCKERFILE: ci/prod/compose/frontend.dockerfile
FRONTEND_ENV: prod FRONTEND_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -22,7 +25,8 @@ steps:
- name: version - name: version
image: alpine:latest image: alpine:latest
commands: commands:
- set -euo pipefail 2>/dev/null || set -eu - set -eu
- if set -o | grep -q pipefail 2>/dev/null; then set -o pipefail; fi
- apk add --no-cache git - apk add --no-cache git
- GIT_REV="$(git rev-parse --short HEAD)" - GIT_REV="$(git rev-parse --short HEAD)"
- BUILD_BRANCH="$(git rev-parse --abbrev-ref HEAD)" - BUILD_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
@@ -50,10 +54,21 @@ steps:
- ./ci/vlt kv_get kv registry user > secrets/REGISTRY_USER - ./ci/vlt kv_get kv registry user > secrets/REGISTRY_USER
- ./ci/vlt kv_get kv registry password > secrets/REGISTRY_PASSWORD - ./ci/vlt kv_get kv registry password > secrets/REGISTRY_PASSWORD
- name: frontend-tests
image: ghcr.io/cirruslabs/flutter:stable
depends_on: [ version ]
commands:
- set -eu
- if set -o | grep -q pipefail 2>/dev/null; then set -o pipefail; fi
- flutter --version
- (cd frontend/pshared && flutter pub get && dart run build_runner build --delete-conflicting-outputs && flutter test)
- (cd frontend/pweb && flutter pub get && dart run build_runner build --delete-conflicting-outputs && flutter test)
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ version, secrets ] depends_on: [ frontend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/frontend/build-image.sh - sh ci/scripts/frontend/build-image.sh
- name: deploy - name: deploy

View File

@@ -8,6 +8,9 @@ matrix:
FX_NEEDS_NATS: "true" FX_NEEDS_NATS: "true"
FX_ENV: prod FX_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -47,6 +50,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh fx_ingestor
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -75,8 +88,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/fx/build-image.sh - sh ci/scripts/fx/build-image.sh
- name: deploy - name: deploy

View File

@@ -8,6 +8,9 @@ matrix:
FX_NEEDS_NATS: "true" FX_NEEDS_NATS: "true"
FX_ENV: prod FX_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -48,6 +51,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh fx_oracle
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -76,8 +89,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/fx/build-image.sh - sh ci/scripts/fx/build-image.sh
- name: deploy - name: deploy

View File

@@ -8,6 +8,9 @@ matrix:
CHAIN_GATEWAY_VAULT_SECRET_PATH: sendico/gateway/chain/vault CHAIN_GATEWAY_VAULT_SECRET_PATH: sendico/gateway/chain/vault
CHAIN_GATEWAY_ENV: prod CHAIN_GATEWAY_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -46,6 +49,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh gateway_chain
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -74,8 +87,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "chain gateway image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/chain_gateway/build-image.sh - sh ci/scripts/chain_gateway/build-image.sh
- name: deploy - name: deploy

View File

@@ -7,6 +7,9 @@ matrix:
MNTX_GATEWAY_NATS_SECRET_PATH: sendico/nats MNTX_GATEWAY_NATS_SECRET_PATH: sendico/nats
MNTX_GATEWAY_MONGO_SECRET_PATH: sendico/db MNTX_GATEWAY_MONGO_SECRET_PATH: sendico/db
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -45,6 +48,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh gateway_mntx
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -73,8 +86,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/mntx/build-image.sh - sh ci/scripts/mntx/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
TGSETTLE_GATEWAY_MONGO_SECRET_PATH: sendico/db TGSETTLE_GATEWAY_MONGO_SECRET_PATH: sendico/db
TGSETTLE_GATEWAY_ENV: prod TGSETTLE_GATEWAY_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -43,6 +46,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh gateway_tgsettle
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -71,8 +84,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/tgsettle/build-image.sh - sh ci/scripts/tgsettle/build-image.sh
- name: deploy - name: deploy

View File

@@ -8,6 +8,9 @@ matrix:
TRON_GATEWAY_VAULT_SECRET_PATH: sendico/gateway/tron/vault TRON_GATEWAY_VAULT_SECRET_PATH: sendico/gateway/tron/vault
TRON_GATEWAY_ENV: prod TRON_GATEWAY_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -46,6 +49,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh gateway_tron
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -74,8 +87,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/tron_gateway/build-image.sh - sh ci/scripts/tron_gateway/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
LEDGER_MONGO_SECRET_PATH: sendico/db LEDGER_MONGO_SECRET_PATH: sendico/db
LEDGER_ENV: prod LEDGER_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -42,6 +45,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh ledger
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -70,8 +83,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/ledger/build-image.sh - sh ci/scripts/ledger/build-image.sh
- name: deploy - name: deploy

View File

@@ -8,6 +8,9 @@ matrix:
NOTIFICATION_TELEGRAM_SECRET_PATH: sendico/notification/telegram NOTIFICATION_TELEGRAM_SECRET_PATH: sendico/notification/telegram
NOTIFICATION_ENV: prod NOTIFICATION_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -45,6 +48,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh notification
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -73,8 +86,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/notification/build-image.sh - sh ci/scripts/notification/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
PAYMENTS_METHODS_MONGO_SECRET_PATH: sendico/db PAYMENTS_METHODS_MONGO_SECRET_PATH: sendico/db
PAYMENTS_METHODS_ENV: prod PAYMENTS_METHODS_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -43,6 +46,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh payments_methods
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -71,8 +84,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/payments_methods/build-image.sh - sh ci/scripts/payments_methods/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
PAYMENTS_MONGO_SECRET_PATH: sendico/db PAYMENTS_MONGO_SECRET_PATH: sendico/db
PAYMENTS_ENV: prod PAYMENTS_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -43,6 +46,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh payments_orchestrator
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -71,8 +84,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/payments_orchestrator/build-image.sh - sh ci/scripts/payments_orchestrator/build-image.sh
- name: deploy - name: deploy

View File

@@ -5,6 +5,9 @@ matrix:
PAYMENTS_QUOTATION_MONGO_SECRET_PATH: sendico/db PAYMENTS_QUOTATION_MONGO_SECRET_PATH: sendico/db
PAYMENTS_QUOTATION_ENV: prod PAYMENTS_QUOTATION_ENV: prod
labels:
platform: linux/amd64
when: when:
- event: push - event: push
branch: main branch: main
@@ -43,6 +46,16 @@ steps:
- export PATH="$(go env GOPATH)/bin:$PATH" - export PATH="$(go env GOPATH)/bin:$PATH"
- bash ci/scripts/proto/generate.sh - bash ci/scripts/proto/generate.sh
- name: backend-lint
image: golang:alpine
depends_on: [ proto ]
commands:
- set -eu
- apk add --no-cache bash git build-base
- CGO_ENABLED=0 go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- export PATH="$(go env GOPATH)/bin:$PATH"
- sh ci/scripts/common/run_backend_lint.sh payments_quotation
- name: backend-tests - name: backend-tests
image: golang:alpine image: golang:alpine
depends_on: [ proto ] depends_on: [ proto ]
@@ -71,8 +84,9 @@ steps:
- name: build-image - name: build-image
image: gcr.io/kaniko-project/executor:debug image: gcr.io/kaniko-project/executor:debug
depends_on: [ backend-tests, secrets ] depends_on: [ backend-lint, backend-tests, secrets ]
commands: commands:
- '[ "$(uname -m)" = "x86_64" ] || { echo "image build requires an amd64 runner"; exit 1; }'
- sh ci/scripts/payments_quotation/build-image.sh - sh ci/scripts/payments_quotation/build-image.sh
- name: deploy - name: deploy

151
Makefile
View File

@@ -1,10 +1,69 @@
# Sendico Development Environment - Makefile # Sendico Development Environment - Makefile
# Docker Compose + Makefile build system # Docker Compose + Makefile build system
.PHONY: help init build up down restart logs rebuild clean vault-init proto generate generate-api generate-frontend update update-api update-frontend test test-api test-frontend .PHONY: \
help \
init \
build \
build-infra \
build-core \
build-fx \
build-payments \
build-gateways \
build-backend \
build-frontend \
up \
down \
restart \
infra-up \
services-up \
backend-up \
backend-down \
backend-rebuild \
status \
list-services \
logs \
rebuild \
clean \
health \
vault-init \
proto \
generate \
generate-backend \
generate-frontend \
update \
update-backend \
update-frontend \
test \
test-backend \
test-frontend \
lint-backend
COMPOSE := docker compose -f docker-compose.dev.yml --env-file .env.dev COMPOSE := docker compose -f docker-compose.dev.yml --env-file .env.dev
SERVICE ?= SERVICE ?=
BACKEND_GOCACHE ?= $(CURDIR)/.gocache
BACKEND_GOLANGCI_LINT_CACHE ?= $(CURDIR)/.golangci-cache
BACKEND_SERVICES := \
dev-discovery \
dev-fx-oracle \
dev-fx-ingestor \
dev-billing-fees \
dev-billing-documents \
dev-ledger \
dev-payments-orchestrator \
dev-payments-quotation \
dev-payments-methods \
dev-chain-gateway-vault-agent \
dev-chain-gateway \
dev-tron-gateway-vault-agent \
dev-tron-gateway \
dev-aurora-gateway \
dev-chsettle-gateway \
dev-notification \
dev-callbacks-vault-agent \
dev-callbacks \
dev-bff-vault-agent \
dev-bff
# Colors # Colors
GREEN := \033[0;32m GREEN := \033[0;32m
@@ -24,34 +83,38 @@ help:
@echo " make down Stop all services" @echo " make down Stop all services"
@echo " make restart Restart all services" @echo " make restart Restart all services"
@echo " make status Show service status" @echo " make status Show service status"
@echo " make logs [SERVICE=x] View logs (all or specific service)" @echo " make logs [SERVICE=dev-ledger] View logs (all or specific service)"
@echo " make rebuild SERVICE=x Rebuild specific service" @echo " make rebuild SERVICE=dev-ledger Rebuild specific service"
@echo " make clean Remove all containers and volumes" @echo " make clean Remove all containers and volumes"
@echo "" @echo ""
@echo "$(YELLOW)Selective Operations:$(NC)" @echo "$(YELLOW)Selective Operations:$(NC)"
@echo " make infra-up Start infrastructure only (mongo, nats, vault)" @echo " make infra-up Start infrastructure only (mongo, nats, vault)"
@echo " make services-up Start application services only" @echo " make services-up Start application services only"
@echo " make backend-up Start backend services only (no infrastructure/frontend)"
@echo " make backend-down Stop backend services only"
@echo " make backend-rebuild Rebuild and restart backend services only"
@echo " make list-services List all available services" @echo " make list-services List all available services"
@echo "" @echo ""
@echo "$(YELLOW)Build Groups:$(NC)" @echo "$(YELLOW)Build Groups:$(NC)"
@echo " make build-core Build core services (discovery, ledger, fees, documents)" @echo " make build-core Build core services (discovery, ledger, fees, documents)"
@echo " make build-fx Build FX services (oracle, ingestor)" @echo " make build-fx Build FX services (oracle, ingestor)"
@echo " make build-payments Build payment orchestrator" @echo " make build-payments Build payment services (orchestrator, quotation, methods)"
@echo " make build-gateways Build gateway services (chain, tron, aurora, tgsettle)" @echo " make build-gateways Build gateway services (chain, tron, aurora, chsettle)"
@echo " make build-api Build API services (notification, callbacks, bff)" @echo " make build-backend Build backend edge services (notification, callbacks, bff)"
@echo " make build-frontend Build Flutter web frontend" @echo " make build-frontend Build Flutter web frontend"
@echo "" @echo ""
@echo "$(YELLOW)Development:$(NC)" @echo "$(YELLOW)Development:$(NC)"
@echo " make proto Generate protobuf code" @echo " make proto Generate protobuf code"
@echo " make generate Generate all code (protobuf + Flutter)" @echo " make generate Generate all code (protobuf + Flutter)"
@echo " make generate-api Generate protobuf code only" @echo " make generate-backend Generate protobuf code only"
@echo " make generate-frontend Generate Flutter code only" @echo " make generate-frontend Generate Flutter code only"
@echo " make update Update all dependencies (Go + Flutter)" @echo " make update Update all dependencies (Go + Flutter)"
@echo " make update-api Update Go dependencies only" @echo " make update-backend Update Go dependencies only"
@echo " make update-frontend Update Flutter dependencies only" @echo " make update-frontend Update Flutter dependencies only"
@echo " make test Run all tests (API + frontend)" @echo " make test Run all tests (backend + frontend)"
@echo " make test-api Run Go API tests only" @echo " make test-backend Run Go backend tests only"
@echo " make test-frontend Run Flutter tests only" @echo " make test-frontend Run Flutter tests only"
@echo " make lint-backend Run golangci-lint across all backend Go modules"
@echo " make health Check service health" @echo " make health Check service health"
@echo "" @echo ""
@echo "Examples:" @echo "Examples:"
@@ -113,7 +176,7 @@ up:
@echo " NATS UI: http://localhost:8222" @echo " NATS UI: http://localhost:8222"
@echo " Vault: http://localhost:8200 (run 'make vault-init' first)" @echo " Vault: http://localhost:8200 (run 'make vault-init' first)"
@echo "" @echo ""
@echo "View logs: make logs [SERVICE=name]" @echo "View logs: make logs [SERVICE=dev-ledger]"
@echo "Stop: make down" @echo "Stop: make down"
# Stop all services # Stop all services
@@ -137,7 +200,7 @@ endif
# Rebuild specific service # Rebuild specific service
rebuild: rebuild:
ifndef SERVICE ifndef SERVICE
$(error SERVICE is required: make rebuild SERVICE=ledger) $(error SERVICE is required: make rebuild SERVICE=dev-ledger)
endif endif
@echo "$(GREEN)Rebuilding $(SERVICE)...$(NC)" @echo "$(GREEN)Rebuilding $(SERVICE)...$(NC)"
@$(COMPOSE) build $(SERVICE) @$(COMPOSE) build $(SERVICE)
@@ -146,13 +209,13 @@ endif
@echo "View logs: make logs SERVICE=$(SERVICE)" @echo "View logs: make logs SERVICE=$(SERVICE)"
# Generate protobuf code (alias) # Generate protobuf code (alias)
proto: generate-api proto: generate-backend
# Generate all code # Generate all code
generate: generate-api generate-frontend generate: generate-backend generate-frontend
# Generate protobuf code # Generate backend protobuf code
generate-api: generate-backend:
@echo "$(GREEN)Generating protobuf code...$(NC)" @echo "$(GREEN)Generating protobuf code...$(NC)"
@./ci/scripts/proto/generate.sh @./ci/scripts/proto/generate.sh
@echo "$(GREEN)✅ Protobuf generation complete$(NC)" @echo "$(GREEN)✅ Protobuf generation complete$(NC)"
@@ -223,12 +286,27 @@ services-up:
dev-chain-gateway \ dev-chain-gateway \
dev-tron-gateway \ dev-tron-gateway \
dev-aurora-gateway \ dev-aurora-gateway \
dev-tgsettle-gateway \ dev-chsettle-gateway \
dev-notification \ dev-notification \
dev-callbacks \ dev-callbacks \
dev-bff \ dev-bff \
dev-frontend dev-frontend
# Backend services only (no infrastructure, no frontend)
backend-up:
@echo "$(GREEN)Starting backend services only (no infra changes)...$(NC)"
@$(COMPOSE) up -d --no-deps $(BACKEND_SERVICES)
backend-down:
@echo "$(YELLOW)Stopping backend services only...$(NC)"
@$(COMPOSE) stop $(BACKEND_SERVICES)
backend-rebuild:
@echo "$(GREEN)Rebuilding backend services only (no infra changes)...$(NC)"
@$(COMPOSE) build $(BACKEND_SERVICES)
@$(COMPOSE) up -d --no-deps --force-recreate $(BACKEND_SERVICES)
@echo "$(GREEN)✅ Backend services rebuilt$(NC)"
# Status check # Status check
status: status:
@$(COMPOSE) ps @$(COMPOSE) ps
@@ -253,7 +331,7 @@ list-services:
@echo " - dev-chain-gateway :50070, :9404 (EVM Blockchain Gateway)" @echo " - dev-chain-gateway :50070, :9404 (EVM Blockchain Gateway)"
@echo " - dev-tron-gateway :50071, :9408 (TRON Blockchain Gateway)" @echo " - dev-tron-gateway :50071, :9408 (TRON Blockchain Gateway)"
@echo " - dev-aurora-gateway :50075, :9405, :8084 (Card Payouts Simulator)" @echo " - dev-aurora-gateway :50075, :9405, :8084 (Card Payouts Simulator)"
@echo " - dev-tgsettle-gateway :50080, :9406 (Telegram Settlements)" @echo " - dev-chsettle-gateway :50080, :9406 (Chimera Settlements Simulator)"
@echo " - dev-notification :8081 (Notifications)" @echo " - dev-notification :8081 (Notifications)"
@echo " - dev-callbacks :9420 (Webhook Callbacks)" @echo " - dev-callbacks :9420 (Webhook Callbacks)"
@echo " - dev-bff :8080 (Backend for Frontend)" @echo " - dev-bff :8080 (Backend for Frontend)"
@@ -283,10 +361,10 @@ build-payments:
build-gateways: build-gateways:
@echo "$(GREEN)Building gateway services...$(NC)" @echo "$(GREEN)Building gateway services...$(NC)"
@$(COMPOSE) build dev-chain-gateway dev-tron-gateway dev-aurora-gateway dev-tgsettle-gateway @$(COMPOSE) build dev-chain-gateway dev-tron-gateway dev-aurora-gateway dev-chsettle-gateway
build-api: build-backend:
@echo "$(GREEN)Building API services...$(NC)" @echo "$(GREEN)Building backend edge services...$(NC)"
@$(COMPOSE) build dev-notification dev-callbacks dev-bff @$(COMPOSE) build dev-notification dev-callbacks dev-bff
build-frontend: build-frontend:
@@ -294,10 +372,10 @@ build-frontend:
@$(COMPOSE) build dev-frontend @$(COMPOSE) build dev-frontend
# Update all dependencies # Update all dependencies
update: update-api update-frontend update: update-backend update-frontend
# Update Go API dependencies # Update Go backend dependencies
update-api: update-backend:
@echo "$(GREEN)Updating Go dependencies...$(NC)" @echo "$(GREEN)Updating Go dependencies...$(NC)"
@for dir in $$(find api -name go.mod -exec dirname {} \;); do \ @for dir in $$(find api -name go.mod -exec dirname {} \;); do \
echo "Updating $$dir..."; \ echo "Updating $$dir..."; \
@@ -313,11 +391,11 @@ update-frontend:
@echo "$(GREEN)✅ Flutter dependencies updated$(NC)" @echo "$(GREEN)✅ Flutter dependencies updated$(NC)"
# Run all tests # Run all tests
test: test-api test-frontend test: test-backend test-frontend
# Run Go API tests # Run Go backend tests
test-api: test-backend:
@echo "$(GREEN)Running API tests...$(NC)" @echo "$(GREEN)Running backend tests...$(NC)"
@failed=""; \ @failed=""; \
for dir in $$(find api -name go.mod -exec dirname {} \;); do \ for dir in $$(find api -name go.mod -exec dirname {} \;); do \
echo "Testing $$dir..."; \ echo "Testing $$dir..."; \
@@ -327,7 +405,7 @@ test-api:
echo "$(YELLOW)Failed:$$failed$(NC)"; \ echo "$(YELLOW)Failed:$$failed$(NC)"; \
exit 1; \ exit 1; \
fi fi
@echo "$(GREEN)✅ All API tests passed$(NC)" @echo "$(GREEN)✅ All backend tests passed$(NC)"
# Run Flutter tests # Run Flutter tests
test-frontend: test-frontend:
@@ -335,3 +413,18 @@ test-frontend:
@cd frontend/pshared && flutter test @cd frontend/pshared && flutter test
@cd frontend/pweb && flutter test @cd frontend/pweb && flutter test
@echo "$(GREEN)✅ All frontend tests passed$(NC)" @echo "$(GREEN)✅ All frontend tests passed$(NC)"
# Run Go backend linting
lint-backend:
@echo "$(GREEN)Running backend linting...$(NC)"
@mkdir -p "$(BACKEND_GOCACHE)" "$(BACKEND_GOLANGCI_LINT_CACHE)"
@failed=""; \
for dir in $$(find api -name go.mod -exec dirname {} \;); do \
echo "Linting $$dir..."; \
(cd "$$dir" && GOCACHE="$(BACKEND_GOCACHE)" GOLANGCI_LINT_CACHE="$(BACKEND_GOLANGCI_LINT_CACHE)" golangci-lint run --allow-serial-runners --allow-parallel-runners ./...) || failed="$$failed $$dir"; \
done; \
if [ -n "$$failed" ]; then \
echo "$(YELLOW)Lint failed:$$failed$(NC)"; \
exit 1; \
fi
@echo "$(GREEN)✅ All backend lint checks passed$(NC)"

View File

@@ -24,13 +24,25 @@ Financial services platform providing payment orchestration, ledger accounting,
| FX Ingestor | `api/fx/ingestor/` | FX rate ingestion | | FX Ingestor | `api/fx/ingestor/` | FX rate ingestion |
| Gateway Chain | `api/gateway/chain/` | EVM blockchain gateway | | Gateway Chain | `api/gateway/chain/` | EVM blockchain gateway |
| Gateway TRON | `api/gateway/tron/` | TRON blockchain gateway | | Gateway TRON | `api/gateway/tron/` | TRON blockchain gateway |
| Gateway Aurora | `api/gateway/aurora/` | Card payouts simulator |
| Gateway ChimeraSettle | `api/gateway/chsettle/` | Dummy settlement simulator (fast/slow/success/fail/stuck) |
| Gateway MNTX | `api/gateway/mntx/` | Card payouts | | Gateway MNTX | `api/gateway/mntx/` | Card payouts |
| Gateway TGSettle | `api/gateway/tgsettle/` | Telegram settlements with MNTX | | Gateway TGSettle (legacy) | `api/gateway/tgsettle/` | Legacy Telegram settlement gateway (not used in dev compose) |
| Notification | `api/notification/` | Notifications | | Notification | `api/notification/` | Notifications |
| BFF | `api/edge/bff/` | Backend for frontend | | BFF | `api/edge/bff/` | Backend for frontend |
| Callbacks | `api/edge/callbacks/` | Webhook callbacks delivery | | Callbacks | `api/edge/callbacks/` | Webhook callbacks delivery |
| Frontend | `frontend/pweb/` | Flutter web UI | | Frontend | `frontend/pweb/` | Flutter web UI |
Gateway note: current dev compose workflows (`make services-up`, `make build-gateways`) use (`chain`, `tron`, `aurora`, `chsettle`). ChimeraSettle is the settlement simulator for test flows; it supports deterministic behavior routing via explicit scenario override and amount buckets. TGSettle remains in-repo as legacy code and is not started by default in dev compose.
## Prerequisites
- Docker with Docker Compose plugin
- GNU Make
- Go toolchain
- Dart SDK
- Flutter SDK
## Development ## Development
Development uses Docker Compose via the Makefile. Run `make help` for all available commands. Development uses Docker Compose via the Makefile. Run `make help` for all available commands.
@@ -54,6 +66,8 @@ make status # Show service status
make logs # View all logs make logs # View all logs
make logs SERVICE=dev-ledger # View logs for a specific service make logs SERVICE=dev-ledger # View logs for a specific service
make rebuild SERVICE=dev-ledger # Rebuild and restart a specific service make rebuild SERVICE=dev-ledger # Rebuild and restart a specific service
make list-services # List all services and ports
make health # Check service health
make clean # Remove all containers and volumes make clean # Remove all containers and volumes
``` ```
@@ -62,6 +76,10 @@ make clean # Remove all containers and volumes
```bash ```bash
make infra-up # Start infrastructure only (MongoDB, NATS, Vault) make infra-up # Start infrastructure only (MongoDB, NATS, Vault)
make services-up # Start application services only (assumes infra is running) make services-up # Start application services only (assumes infra is running)
make backend-up # Start backend services only (no infrastructure/frontend changes)
make backend-down # Stop backend services only
make backend-rebuild # Rebuild and restart backend services only
make list-services # Show service names, ports, and descriptions
``` ```
### Build Groups ### Build Groups
@@ -69,9 +87,9 @@ make services-up # Start application services only (assumes infra is running)
```bash ```bash
make build-core # discovery, ledger, fees, documents make build-core # discovery, ledger, fees, documents
make build-fx # oracle, ingestor make build-fx # oracle, ingestor
make build-payments # orchestrator make build-payments # orchestrator, quotation, methods
make build-gateways # chain, tron, mntx, tgsettle make build-gateways # chain, tron, aurora, chsettle
make build-api # notification, callbacks, bff make build-backend # notification, callbacks, bff
make build-frontend # Flutter web UI make build-frontend # Flutter web UI
``` ```
@@ -79,24 +97,43 @@ make build-frontend # Flutter web UI
```bash ```bash
make generate # Generate all code (protobuf + Flutter) make generate # Generate all code (protobuf + Flutter)
make generate-api # Generate protobuf code only make generate-backend # Generate protobuf code only
make generate-frontend # Generate Flutter code only (build_runner) make generate-frontend # Generate Flutter code only (build_runner)
make proto # Alias for generate-api make proto # Alias for generate-backend
``` ```
### Testing ### Testing
```bash ```bash
make test # Run all tests (API + frontend) make test # Run all tests (backend + frontend)
make test-api # Run Go API tests only make test-backend # Run Go backend tests only
make test-frontend # Run Flutter tests only make test-frontend # Run Flutter tests only
``` ```
### Backend CI Bypass Tags
Backend Woodpecker module pipelines now run both lint and tests before image build/deploy.
If you intentionally need to bypass checks for a specific commit, include one of these tags in the commit message:
```text
[skip-lint:<service>] # Skip lint for one backend service pipeline
[skip-lint] # Skip lint for all backend service pipelines
[skip-tests:<service>] # Skip tests for one backend service pipeline
[skip-tests] # Skip tests for all backend service pipelines
[skip-autotests:<service>] # Alias for skip-tests:<service>
[skip-autotests] # Alias for skip-tests
[skip-checks:<service>] # Skip both lint and tests for one backend service pipeline
[skip-checks] # Skip both lint and tests for all backend service pipelines
```
`<service>` must match the backend pipeline key used by CI:
`bff`, `callbacks`, `billing_documents`, `billing_fees`, `discovery`, `fx_ingestor`, `fx_oracle`, `gateway_chain`, `gateway_mntx`, `gateway_tgsettle`, `gateway_tron`, `ledger`, `notification`, `payments_methods`, `payments_orchestrator`, `payments_quotation`.
### Update Dependencies ### Update Dependencies
```bash ```bash
make update # Update all Go and Flutter dependencies make update # Update all Go and Flutter dependencies
make update-api # Update Go dependencies only make update-backend # Update Go dependencies only
make update-frontend # Update Flutter dependencies only make update-frontend # Update Flutter dependencies only
``` ```

View File

@@ -137,10 +137,11 @@ make infra-up
make services-up make services-up
# Or start specific service groups # Or start specific service groups
make build-core # discovery, ledger, billing-fees make build-core # discovery, ledger, billing-fees, billing-documents
make build-fx # fx-oracle, fx-ingestor make build-fx # fx-oracle, fx-ingestor
make build-payments # payments-orchestrator make build-payments # payments-orchestrator, payments-quotation, payments-methods
make build-gateways # chain, mntx, tgsettle make build-gateways # chain, tron, aurora, chsettle
make build-backend # notification, callbacks, bff
``` ```
--- ---

View File

@@ -1,196 +1,47 @@
# See the dedicated "version" documentation section.
version: "2" version: "2"
linters: linters:
# Default set of linters. default: none
# The value can be:
# - `standard`: https://golangci-lint.run/docs/linters/#enabled-by-default
# - `all`: enables all linters by default.
# - `none`: disables all linters by default.
# - `fast`: enables only linters considered as "fast" (`golangci-lint help linters --json | jq '[ .[] | select(.fast==true) ] | map(.name)'`).
# Default: standard
default: all
# Enable specific linter.
enable: enable:
- arangolint
- asasalint
- asciicheck
- bidichk
- bodyclose - bodyclose
- canonicalheader - canonicalheader
- containedctx
- contextcheck
- copyloopvar - copyloopvar
- cyclop
- decorder
- dogsled
- dupl
- dupword
- durationcheck - durationcheck
- embeddedstructfieldcheck
- err113
- errcheck - errcheck
- errchkjson - errchkjson
- errname - errname
- errorlint - errorlint
- exhaustive
- exptostd
- fatcontext
- forbidigo
- forcetypeassert
- funcorder
- funlen
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gochecksumtype
- gocognit
- goconst
- gocritic
- gocyclo
- godoclint
- godot
- godox
- goheader
- gomodguard
- goprintffuncname
- gosec - gosec
- gosmopolitan
- govet - govet
- grouper
- iface
- importas
- inamedparam
- ineffassign - ineffassign
- interfacebloat
- intrange
- iotamixing
- ireturn
- lll
- loggercheck
- maintidx
- makezero
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
- nilerr - nilerr
- nilnesserr - nilnesserr
- nilnil - nilnil
- nlreturn
- noctx - noctx
- noinlineerr
- nolintlint
- nonamedreturns
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- recvcheck
- revive
- rowserrcheck - rowserrcheck
- sloglint
- spancheck
- sqlclosecheck - sqlclosecheck
- staticcheck - staticcheck
- tagalign
- tagliatelle
- testableexamples
- testifylint
- testpackage
- thelper
- tparallel
- unconvert - unconvert
- unparam
- unqueryvet
- unused
- usestdlibvars
- usetesting
- varnamelen
- wastedassign - wastedassign
- whitespace
- wsl_v5
- zerologlint
# Disable specific linters.
disable: disable:
- depguard - depguard
- exhaustruct - exhaustruct
- gochecknoglobals - gochecknoglobals
- gochecknoinits
- gomoddirectives - gomoddirectives
- wsl
- wrapcheck - wrapcheck
# All available settings of specific linters. - cyclop
# See the dedicated "linters.settings" documentation section. - dupl
settings: - funlen
wsl_v5: - gocognit
allow-first-in-block: true - gocyclo
allow-whole-block: false - ireturn
branch-max-lines: 2 - lll
- mnd
# Defines a set of rules to ignore issues. - nestif
# It does not skip the analysis, and so does not ignore "typecheck" errors. - nlreturn
exclusions: - noinlineerr
# Mode of the generated files analysis. - paralleltest
# - tagliatelle
# - `strict`: sources are excluded by strictly following the Go generated file convention. - testpackage
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$` - varnamelen
# This line must appear before the first non-comment, non-blank text in the file. - wsl_v5
# https://go.dev/s/generatedcode
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
# - `disable`: disable the generated files exclusion.
#
# Default: strict
generated: lax
# Log a warning if an exclusion rule is unused.
# Default: false
warn-unused: true
# Predefined exclusion rules.
# Default: []
presets:
- comments
- std-error-handling
- common-false-positives
- legacy
# Excluding configuration per-path, per-linter, per-text and per-source.
rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- funlen
- gocyclo
- errcheck
- dupl
- gosec
# Run some linter only for test files by excluding its issues for everything else.
- path-except: _test\.go
linters:
- forbidigo
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via `nolint` comments.
# `/` will be replaced by the current OS file path separator to properly work on Windows.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
# Exclude some `staticcheck` messages.
- linters:
- staticcheck
text: "SA9003:"
# Exclude `lll` issues for long lines with `go:generate`.
- linters:
- lll
source: "^//go:generate "
# Which file paths to exclude: they will be analyzed, but issues from them won't be reported.
# "/" will be replaced by the current OS file path separator to properly work on Windows.
# Default: []
paths: []
# Which file paths to not exclude.
# Default: []
paths-except: []

View File

@@ -24,8 +24,6 @@ database:
documents: documents:
issuer: issuer:
legal_name: "Sendico Ltd"
legal_address: "12 Market Street, London, UK"
logo_path: "assets/logo.png" logo_path: "assets/logo.png"
templates: templates:
acceptance_path: "templates/acceptance.tpl" acceptance_path: "templates/acceptance.tpl"

View File

@@ -24,8 +24,6 @@ database:
documents: documents:
issuer: issuer:
legal_name: "Sendico Ltd"
legal_address: "12 Market Street, London, UK"
logo_path: "/app/assets/logo.png" logo_path: "/app/assets/logo.png"
templates: templates:
acceptance_path: "/app/templates/acceptance.tpl" acceptance_path: "/app/templates/acceptance.tpl"

View File

@@ -8,14 +8,14 @@ require (
github.com/aws/aws-sdk-go-v2 v1.41.3 github.com/aws/aws-sdk-go-v2 v1.41.3
github.com/aws/aws-sdk-go-v2/config v1.32.11 github.com/aws/aws-sdk-go-v2/config v1.32.11
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 github.com/aws/aws-sdk-go-v2/credentials v1.19.11
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4
github.com/jung-kurt/gofpdf v1.16.2 github.com/jung-kurt/gofpdf v1.16.2
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/shopspring/decimal v1.4.0 github.com/shopspring/decimal v1.4.0
github.com/tech/sendico/pkg v0.1.0 github.com/tech/sendico/pkg v0.1.0
go.mongodb.org/mongo-driver/v2 v2.5.0 go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1 google.golang.org/grpc v1.79.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -25,7 +25,7 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect
@@ -59,11 +59,11 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect

View File

@@ -20,8 +20,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJ
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 h1:3Y4oma5TiV7tT9wa8zRcdoXwZkGz9Q/wxbEUK7cMuAM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 h1:qi3e/dmpdONhj1RyIZdi6DKKpDXS5Lb8ftr3p7cyHJc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
@@ -30,8 +30,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7su
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3 h1:+d0SsTvxtIJt4tSJ6wr+jrxEMDa6XeupjRv8H7Qitkk= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3/go.mod h1:ROUNFvFWPwBlOu687WJNQ9cPvd2ccpFrnCiA1YGz50o= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias= github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
@@ -217,8 +217,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -233,16 +233,16 @@ golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210615035016-665e8c7367d1/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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -260,8 +260,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,116 @@
package content
// Issuer details are intentionally centralized to avoid document text drift.
const (
IssuerLegalName = "SMX Operations Limited"
IssuerLegalAddress = "Room 607, 12/F., Block C, Hong Kong Industrial Centre, 489-491 Castle Peak Road, Lai Chi Kok, Hong Kong"
)
const (
PDFTitleActOfAcceptance = "Act of Acceptance"
DocumentIntegrityHashPrefix = "Document integrity hash: "
)
// AcceptanceTemplateContent contains all static copy used by the acceptance act template.
type AcceptanceTemplateContent struct {
Title string
Subtitle string
MetaDateLabel string
MetaActNumberLabel string
SectionParties string
PartiesIntro string
PartyExecutorLabel string
PartyStatusLabel string
PartyStatusValue string
SectionBasis string
BasisLine1 string
BasisLine2 string
SectionServicesRendered string
ServicesRenderedLine1 string
ServicesRenderedLine2 string
SectionRemuneration string
RemunerationHeaderDesc string
RemunerationHeaderAmount string
RemunerationServicesDesc string
SectionConfirmation string
ConfirmationLine1 string
ConfirmationLine2 string
ConfirmationPaymentLine1 string
ConfirmationPaymentLine2 string
SectionSignatures string
SignatureCustomerLine string
SignatureExecutorLine string
}
var AcceptanceTemplate = AcceptanceTemplateContent{
Title: "ACT OF ACCEPTANCE OF SERVICES",
Subtitle: "under the Public Offer Agreement",
MetaDateLabel: "Date",
MetaActNumberLabel: "Act No",
SectionParties: "PARTIES",
PartiesIntro: "This Act is made between the following Parties.",
PartyExecutorLabel: "Executor",
PartyStatusLabel: "Status",
PartyStatusValue: "Individual",
SectionBasis: "BASIS",
BasisLine1: "This Act is issued pursuant to the Public Offer Agreement",
BasisLine2: "accepted by the Executor by joining the offer.",
SectionServicesRendered: "SERVICES RENDERED",
ServicesRenderedLine1: "The Executor has rendered services to the Customer",
ServicesRenderedLine2: "in accordance with the terms of the Public Offer Agreement.",
SectionRemuneration: "REMUNERATION",
RemunerationHeaderDesc: "Description",
RemunerationHeaderAmount: "Amount",
RemunerationServicesDesc: "Services rendered under the Public Offer Agreement",
SectionConfirmation: "CONFIRMATION",
ConfirmationLine1: "The Customer confirms that the services were rendered properly",
ConfirmationLine2: "and accepted without any claims.",
ConfirmationPaymentLine1: "The remuneration for the services was paid to the Executor",
ConfirmationPaymentLine2: "using the bank card details provided by the Executor.",
SectionSignatures: "SIGNATURES",
SignatureCustomerLine: "Customer ___________________________",
SignatureExecutorLine: "Executor ___________________________",
}
// OperationDocumentContent contains all static copy for operation documents.
type OperationDocumentContent struct {
Title string
Subtitle string
MetaDocumentType string
SectionOperation string
SectionFailure string
RowOrganization string
RowGatewayService string
RowOperationRef string
RowPaymentRef string
RowCode string
RowState string
RowLabel string
RowStartedAtUTC string
RowCompletedAtUTC string
RowAmount string
RowFailureCode string
RowFailureReason string
MissingValuePlaceholder string
}
var OperationDocument = OperationDocumentContent{
Title: "OPERATION BILLING DOCUMENT",
Subtitle: "Gateway operation statement",
MetaDocumentType: "Document Type: Operation",
SectionOperation: "OPERATION DETAILS",
SectionFailure: "FAILURE DETAILS",
RowOrganization: "Organization",
RowGatewayService: "Gateway Service",
RowOperationRef: "Operation Ref",
RowPaymentRef: "Payment Ref",
RowCode: "Code",
RowState: "State",
RowLabel: "Label",
RowStartedAtUTC: "Started At (UTC)",
RowCompletedAtUTC: "Completed At (UTC)",
RowAmount: "Amount",
RowFailureCode: "Failure Code",
RowFailureReason: "Failure Reason",
MissingValuePlaceholder: "n/a",
}

View File

@@ -2,6 +2,7 @@ package docstore
import ( import (
"context" "context"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@@ -36,8 +37,12 @@ func (s *LocalStore) Save(ctx context.Context, key string, data []byte) error {
return err return err
} }
path := filepath.Join(s.rootPath, filepath.Clean(key)) path, err := resolveStoragePath(s.rootPath, key)
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
s.logger.Warn("Failed to create document directory", zap.Error(err), zap.String("path", path)) s.logger.Warn("Failed to create document directory", zap.Error(err), zap.String("path", path))
return err return err
@@ -56,8 +61,12 @@ func (s *LocalStore) Load(ctx context.Context, key string) ([]byte, error) {
return nil, err return nil, err
} }
path := filepath.Join(s.rootPath, filepath.Clean(key)) path, err := resolveStoragePath(s.rootPath, key)
if err != nil {
return nil, err
}
//nolint:gosec // path is constrained by resolveStoragePath to stay within configured root.
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
s.logger.Warn("Failed to read document file", zap.Error(err), zap.String("path", path)) s.logger.Warn("Failed to read document file", zap.Error(err), zap.String("path", path))
@@ -69,3 +78,32 @@ func (s *LocalStore) Load(ctx context.Context, key string) ([]byte, error) {
} }
var _ Store = (*LocalStore)(nil) var _ Store = (*LocalStore)(nil)
func resolveStoragePath(rootPath string, key string) (string, error) {
cleanKey := filepath.Clean(strings.TrimSpace(key))
if cleanKey == "" || cleanKey == "." {
return "", merrors.InvalidArgument("docstore: key is required")
}
if filepath.IsAbs(cleanKey) {
return "", merrors.InvalidArgument("docstore: absolute keys are not allowed")
}
rootAbs, err := filepath.Abs(rootPath)
if err != nil {
return "", fmt.Errorf("resolve local store root: %w", err)
}
path := filepath.Join(rootAbs, cleanKey)
pathAbs, err := filepath.Abs(path)
if err != nil {
return "", fmt.Errorf("resolve local store path: %w", err)
}
prefix := rootAbs + string(filepath.Separator)
if pathAbs != rootAbs && !strings.HasPrefix(pathAbs, prefix) {
return "", merrors.InvalidArgument("docstore: key escapes root path")
}
return pathAbs, nil
}

View File

@@ -124,7 +124,11 @@ func (s *S3Store) Load(ctx context.Context, key string) ([]byte, error) {
return nil, err return nil, err
} }
defer obj.Body.Close() defer func() {
if closeErr := obj.Body.Close(); closeErr != nil {
s.logger.Warn("Failed to close document body", zap.Error(closeErr), zap.String("key", key))
}
}()
return io.ReadAll(obj.Body) return io.ReadAll(obj.Body)
} }

View File

@@ -3,18 +3,24 @@ package documents
import ( import (
"strings" "strings"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/internal/docstore" "github.com/tech/sendico/billing/documents/internal/docstore"
"github.com/tech/sendico/billing/documents/renderer" "github.com/tech/sendico/billing/documents/renderer"
) )
// Config holds document service settings loaded from YAML. // Config holds document service settings loaded from YAML.
type Config struct { type Config struct {
Issuer renderer.Issuer `yaml:"issuer"` Issuer IssuerConfig `yaml:"issuer"`
Templates TemplateConfig `yaml:"templates"` Templates TemplateConfig `yaml:"templates"`
Protection ProtectionConfig `yaml:"protection"` Protection ProtectionConfig `yaml:"protection"`
Storage docstore.Config `yaml:"storage"` Storage docstore.Config `yaml:"storage"`
} }
// IssuerConfig defines issuer settings that are environment-specific.
type IssuerConfig struct {
LogoPath string `yaml:"logo_path"`
}
// TemplateConfig defines document template locations. // TemplateConfig defines document template locations.
type TemplateConfig struct { type TemplateConfig struct {
AcceptancePath string `yaml:"acceptance_path"` AcceptancePath string `yaml:"acceptance_path"`
@@ -25,6 +31,14 @@ type ProtectionConfig struct {
OwnerPassword string `yaml:"owner_password"` OwnerPassword string `yaml:"owner_password"`
} }
func (c Config) IssuerDetails() renderer.Issuer {
return renderer.Issuer{
LegalName: content.IssuerLegalName,
LegalAddress: content.IssuerLegalAddress,
LogoPath: c.Issuer.LogoPath,
}
}
func (c Config) AcceptanceTemplatePath() string { func (c Config) AcceptanceTemplatePath() string {
if strings.TrimSpace(c.Templates.AcceptancePath) == "" { if strings.TrimSpace(c.Templates.AcceptancePath) == "" {
return "templates/acceptance.tpl" return "templates/acceptance.tpl"

View File

@@ -7,7 +7,6 @@ import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/client_golang/prometheus/promauto"
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
) )
@@ -17,7 +16,6 @@ var (
requestsTotal *prometheus.CounterVec requestsTotal *prometheus.CounterVec
requestLatency *prometheus.HistogramVec requestLatency *prometheus.HistogramVec
batchSize prometheus.Histogram
documentBytes *prometheus.HistogramVec documentBytes *prometheus.HistogramVec
) )
@@ -44,16 +42,6 @@ func initMetrics() {
[]string{"call", "status", "doc_type"}, []string{"call", "status", "doc_type"},
) )
batchSize = promauto.NewHistogram(
prometheus.HistogramOpts{
Namespace: "billing",
Subsystem: "documents",
Name: "batch_size",
Help: "Number of payment references in batch resolution requests.",
Buckets: []float64{0, 1, 2, 5, 10, 20, 50, 100, 250, 500},
},
)
documentBytes = promauto.NewHistogramVec( documentBytes = promauto.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Namespace: "billing", Namespace: "billing",
@@ -67,18 +55,14 @@ func initMetrics() {
}) })
} }
func observeRequest(call string, docType documentsv1.DocumentType, statusLabel string, took time.Duration) { func observeRequest(call string, documentKind, statusLabel string, took time.Duration) {
typeLabel := docTypeLabel(docType) kind := docKindLabel(documentKind)
requestsTotal.WithLabelValues(call, statusLabel, typeLabel).Inc() requestsTotal.WithLabelValues(call, statusLabel, kind).Inc()
requestLatency.WithLabelValues(call, statusLabel, typeLabel).Observe(took.Seconds()) requestLatency.WithLabelValues(call, statusLabel, kind).Observe(took.Seconds())
} }
func observeBatchSize(size int) { func observeDocumentBytes(documentKind string, size int) {
batchSize.Observe(float64(size)) documentBytes.WithLabelValues(docKindLabel(documentKind)).Observe(float64(size))
}
func observeDocumentBytes(docType documentsv1.DocumentType, size int) {
documentBytes.WithLabelValues(docTypeLabel(docType)).Observe(float64(size))
} }
func statusFromError(err error) string { func statusFromError(err error) string {
@@ -100,10 +84,10 @@ func statusFromError(err error) string {
return strings.ToLower(code.String()) return strings.ToLower(code.String())
} }
func docTypeLabel(docType documentsv1.DocumentType) string { func docKindLabel(documentKind string) string {
label := docType.String() label := strings.TrimSpace(documentKind)
if label == "" { if label == "" {
return "DOCUMENT_TYPE_UNSPECIFIED" return "operation"
} }
return label return label

View File

@@ -5,11 +5,11 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"path/filepath"
"strings" "strings"
"time" "time"
"github.com/tech/sendico/billing/documents/internal/appversion" "github.com/tech/sendico/billing/documents/internal/appversion"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/internal/docstore" "github.com/tech/sendico/billing/documents/internal/docstore"
"github.com/tech/sendico/billing/documents/renderer" "github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage" "github.com/tech/sendico/billing/documents/storage"
@@ -145,94 +145,6 @@ func (s *Service) Shutdown() {
} }
} }
func (s *Service) BatchResolveDocuments(ctx context.Context, req *documentsv1.BatchResolveDocumentsRequest) (resp *documentsv1.BatchResolveDocumentsResponse, err error) {
start := time.Now()
paymentRefs := 0
if req != nil {
paymentRefs = len(req.GetPaymentRefs())
}
logger := s.logger.With(zap.Int("payment_refs", paymentRefs))
defer func() {
statusLabel := statusFromError(err)
observeRequest("batch_resolve", documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED, statusLabel, time.Since(start))
observeBatchSize(paymentRefs)
itemsCount := 0
if resp != nil {
itemsCount = len(resp.GetItems())
}
fields := []zap.Field{
zap.String("status", statusLabel),
zap.Duration("duration", time.Since(start)),
zap.Int("items", itemsCount),
}
if err != nil {
logger.Warn("BatchResolveDocuments failed", append(fields, zap.Error(err))...)
return
}
logger.Info("BatchResolveDocuments finished", fields...)
}()
_ = ctx
err = status.Error(codes.Unimplemented, "payment-level document flow removed; use GetOperationDocument")
return nil, err
}
func (s *Service) GetDocument(ctx context.Context, req *documentsv1.GetDocumentRequest) (resp *documentsv1.GetDocumentResponse, err error) {
start := time.Now()
docType := documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED
paymentRef := ""
if req != nil {
docType = req.GetType()
paymentRef = strings.TrimSpace(req.GetPaymentRef())
}
logger := s.logger.With(
zap.String("payment_ref", paymentRef),
zap.String("document_type", docTypeLabel(docType)),
)
defer func() {
statusLabel := statusFromError(err)
observeRequest("get_document", docType, statusLabel, time.Since(start))
if resp != nil {
observeDocumentBytes(docType, len(resp.GetContent()))
}
contentBytes := 0
if resp != nil {
contentBytes = len(resp.GetContent())
}
fields := []zap.Field{
zap.String("status", statusLabel),
zap.Duration("duration", time.Since(start)),
zap.Int("content_bytes", contentBytes),
}
if err != nil {
logger.Warn("GetDocument failed", append(fields, zap.Error(err))...)
return
}
logger.Info("GetDocument finished", fields...)
}()
_ = ctx
err = status.Error(codes.Unimplemented, "payment-level document flow removed; use GetOperationDocument")
return nil, err
}
func (s *Service) GetOperationDocument(_ context.Context, req *documentsv1.GetOperationDocumentRequest) (resp *documentsv1.GetDocumentResponse, err error) { func (s *Service) GetOperationDocument(_ context.Context, req *documentsv1.GetOperationDocumentRequest) (resp *documentsv1.GetDocumentResponse, err error) {
start := time.Now() start := time.Now()
organizationRef := "" organizationRef := ""
@@ -253,11 +165,10 @@ func (s *Service) GetOperationDocument(_ context.Context, req *documentsv1.GetOp
defer func() { defer func() {
statusLabel := statusFromError(err) statusLabel := statusFromError(err)
docType := documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED observeRequest("get_operation_document", "operation", statusLabel, time.Since(start))
observeRequest("get_operation_document", docType, statusLabel, time.Since(start))
if resp != nil { if resp != nil {
observeDocumentBytes(docType, len(resp.GetContent())) observeDocumentBytes("operation", len(resp.GetContent()))
} }
contentBytes := 0 contentBytes := 0
@@ -336,18 +247,6 @@ func (s *Service) startDiscoveryAnnouncer() {
s.announcer.Start() s.announcer.Start()
} }
type serviceError string
func (e serviceError) Error() string {
return string(e)
}
var (
errStorageUnavailable = serviceError("documents: storage not initialised")
errDocStoreUnavailable = serviceError("documents: document store not initialised")
errTemplateUnavailable = serviceError("documents: template renderer not initialised")
)
func (s *Service) generateActPDF(snapshot model.ActSnapshot) ([]byte, string, error) { func (s *Service) generateActPDF(snapshot model.ActSnapshot) ([]byte, string, error) {
blocks, err := s.template.Render(snapshot) blocks, err := s.template.Render(snapshot)
if err != nil { if err != nil {
@@ -363,7 +262,7 @@ func (s *Service) generateOperationPDF(snapshot operationSnapshot) ([]byte, stri
func (s *Service) renderPDFWithIntegrity(blocks []renderer.Block) ([]byte, string, error) { func (s *Service) renderPDFWithIntegrity(blocks []renderer.Block) ([]byte, string, error) {
generated := renderer.Renderer{ generated := renderer.Renderer{
Issuer: s.config.Issuer, Issuer: s.config.IssuerDetails(),
OwnerPassword: s.config.Protection.OwnerPassword, OwnerPassword: s.config.Protection.OwnerPassword,
} }
@@ -427,39 +326,41 @@ func operationSnapshotFromRequest(req *documentsv1.GetOperationDocumentRequest)
} }
func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block { func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
documentCopy := content.OperationDocument
rows := [][]string{ rows := [][]string{
{"Organization", snapshot.OrganizationRef}, {documentCopy.RowOrganization, snapshot.OrganizationRef},
{"Gateway Service", snapshot.GatewayService}, {documentCopy.RowGatewayService, snapshot.GatewayService},
{"Operation Ref", snapshot.OperationRef}, {documentCopy.RowOperationRef, snapshot.OperationRef},
{"Payment Ref", safeValue(snapshot.PaymentRef)}, {documentCopy.RowPaymentRef, safeValue(snapshot.PaymentRef)},
{"Code", safeValue(snapshot.OperationCode)}, {documentCopy.RowCode, safeValue(snapshot.OperationCode)},
{"State", safeValue(snapshot.OperationState)}, {documentCopy.RowState, safeValue(snapshot.OperationState)},
{"Label", safeValue(snapshot.OperationLabel)}, {documentCopy.RowLabel, safeValue(snapshot.OperationLabel)},
{"Started At (UTC)", formatSnapshotTime(snapshot.StartedAt)}, {documentCopy.RowStartedAtUTC, formatSnapshotTime(snapshot.StartedAt)},
{"Completed At (UTC)", formatSnapshotTime(snapshot.CompletedAt)}, {documentCopy.RowCompletedAtUTC, formatSnapshotTime(snapshot.CompletedAt)},
} }
if snapshot.Amount != "" || snapshot.Currency != "" { if snapshot.Amount != "" || snapshot.Currency != "" {
rows = append(rows, []string{"Amount", strings.TrimSpace(strings.TrimSpace(snapshot.Amount) + " " + strings.TrimSpace(snapshot.Currency))}) rows = append(rows, []string{documentCopy.RowAmount, strings.TrimSpace(strings.TrimSpace(snapshot.Amount) + " " + strings.TrimSpace(snapshot.Currency))})
} }
blocks := []renderer.Block{ blocks := []renderer.Block{
{ {
Tag: renderer.TagTitle, Tag: renderer.TagTitle,
Lines: []string{"OPERATION BILLING DOCUMENT"}, Lines: []string{documentCopy.Title},
}, },
{ {
Tag: renderer.TagSubtitle, Tag: renderer.TagSubtitle,
Lines: []string{"Gateway operation statement"}, Lines: []string{documentCopy.Subtitle},
}, },
{ {
Tag: renderer.TagMeta, Tag: renderer.TagMeta,
Lines: []string{ Lines: []string{
"Document Type: Operation", documentCopy.MetaDocumentType,
}, },
}, },
{ {
Tag: renderer.TagSection, Tag: renderer.TagSection,
Lines: []string{"OPERATION DETAILS"}, Lines: []string{documentCopy.SectionOperation},
}, },
{ {
Tag: renderer.TagKV, Tag: renderer.TagKV,
@@ -469,12 +370,12 @@ func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
if snapshot.FailureCode != "" || snapshot.FailureReason != "" { if snapshot.FailureCode != "" || snapshot.FailureReason != "" {
blocks = append(blocks, blocks = append(blocks,
renderer.Block{Tag: renderer.TagSection, Lines: []string{"FAILURE DETAILS"}}, renderer.Block{Tag: renderer.TagSection, Lines: []string{documentCopy.SectionFailure}},
renderer.Block{ renderer.Block{
Tag: renderer.TagKV, Tag: renderer.TagKV,
Rows: [][]string{ Rows: [][]string{
{"Failure Code", safeValue(snapshot.FailureCode)}, {documentCopy.RowFailureCode, safeValue(snapshot.FailureCode)},
{"Failure Reason", safeValue(snapshot.FailureReason)}, {documentCopy.RowFailureReason, safeValue(snapshot.FailureReason)},
}, },
}, },
) )
@@ -485,7 +386,7 @@ func buildOperationBlocks(snapshot operationSnapshot) []renderer.Block {
func formatSnapshotTime(value time.Time) string { func formatSnapshotTime(value time.Time) string {
if value.IsZero() { if value.IsZero() {
return "n/a" return content.OperationDocument.MissingValuePlaceholder
} }
return value.UTC().Format(time.RFC3339) return value.UTC().Format(time.RFC3339)
@@ -494,7 +395,7 @@ func formatSnapshotTime(value time.Time) string {
func safeValue(value string) string { func safeValue(value string) string {
trimmed := strings.TrimSpace(value) trimmed := strings.TrimSpace(value)
if trimmed == "" { if trimmed == "" {
return "n/a" return content.OperationDocument.MissingValuePlaceholder
} }
return trimmed return trimmed
@@ -535,50 +436,3 @@ func sanitizeFilenameComponent(value string) string {
return strings.Trim(b.String(), "_") return strings.Trim(b.String(), "_")
} }
func toProtoTypes(types []model.DocumentType) []documentsv1.DocumentType {
if len(types) == 0 {
return nil
}
result := make([]documentsv1.DocumentType, 0, len(types))
for _, t := range types {
result = append(result, t.Proto())
}
return result
}
func documentStoragePath(paymentRef string, docType documentsv1.DocumentType) string {
suffix := "document.pdf"
switch docType {
case documentsv1.DocumentType_DOCUMENT_TYPE_ACT:
suffix = "act.pdf"
case documentsv1.DocumentType_DOCUMENT_TYPE_INVOICE:
suffix = "invoice.pdf"
case documentsv1.DocumentType_DOCUMENT_TYPE_RECEIPT:
suffix = "receipt.pdf"
case documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED:
// default suffix used
}
return filepath.ToSlash(filepath.Join("documents", paymentRef, suffix))
}
func documentFilename(docType documentsv1.DocumentType, paymentRef string) string {
name := "document"
switch docType {
case documentsv1.DocumentType_DOCUMENT_TYPE_ACT:
name = "act"
case documentsv1.DocumentType_DOCUMENT_TYPE_INVOICE:
name = "invoice"
case documentsv1.DocumentType_DOCUMENT_TYPE_RECEIPT:
name = "receipt"
case documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED:
// default name used
}
return fmt.Sprintf("%s_%s.pdf", name, paymentRef)
}

View File

@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/renderer" "github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage" "github.com/tech/sendico/billing/documents/storage"
"github.com/tech/sendico/billing/documents/storage/model" "github.com/tech/sendico/billing/documents/storage/model"
@@ -53,38 +54,6 @@ func (s *stubDocumentsStore) ListByPaymentRefs(_ context.Context, _ []string) ([
var _ storage.DocumentsStore = (*stubDocumentsStore)(nil) var _ storage.DocumentsStore = (*stubDocumentsStore)(nil)
type memDocStore struct {
data map[string][]byte
saveCount int
loadCount int
}
func newMemDocStore() *memDocStore {
return &memDocStore{data: map[string][]byte{}}
}
func (m *memDocStore) Save(_ context.Context, key string, data []byte) error {
m.saveCount++
copyData := make([]byte, len(data))
copy(copyData, data)
m.data[key] = copyData
return nil
}
func (m *memDocStore) Load(_ context.Context, key string) ([]byte, error) {
m.loadCount++
data := m.data[key]
copyData := make([]byte, len(data))
copy(copyData, data)
return copyData, nil
}
func (m *memDocStore) Counts() (int, int) {
return m.saveCount, m.loadCount
}
type stubTemplate struct { type stubTemplate struct {
blocks []renderer.Block blocks []renderer.Block
calls int calls int
@@ -112,15 +81,7 @@ func TestGenerateActPDF_IdempotentAndHashed(t *testing.T) {
}, },
} }
cfg := Config{
Issuer: renderer.Issuer{
LegalName: "Sendico Ltd",
LegalAddress: "12 Market Street, London, UK",
},
}
svc := NewService(zap.NewNop(), nil, nil, svc := NewService(zap.NewNop(), nil, nil,
WithConfig(cfg),
WithTemplateRenderer(tmpl), WithTemplateRenderer(tmpl),
) )
@@ -164,7 +125,7 @@ func TestGenerateActPDF_IdempotentAndHashed(t *testing.T) {
} }
func extractFooterHash(pdf []byte) string { func extractFooterHash(pdf []byte) string {
prefix := []byte("Document integrity hash: ") prefix := []byte(content.DocumentIntegrityHashPrefix)
idx := bytes.Index(pdf, prefix) idx := bytes.Index(pdf, prefix)
if idx == -1 { if idx == -1 {
@@ -191,11 +152,7 @@ func isHexDigit(b byte) bool {
} }
func TestGetOperationDocument_GeneratesPDF(t *testing.T) { func TestGetOperationDocument_GeneratesPDF(t *testing.T) {
svc := NewService(zap.NewNop(), nil, nil, WithConfig(Config{ svc := NewService(zap.NewNop(), nil, nil)
Issuer: renderer.Issuer{
LegalName: "Sendico Ltd",
},
}))
resp, err := svc.GetOperationDocument(context.Background(), &documentsv1.GetOperationDocumentRequest{ resp, err := svc.GetOperationDocument(context.Background(), &documentsv1.GetOperationDocumentRequest{
OrganizationRef: "org-1", OrganizationRef: "org-1",

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/renderer" "github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage/model" "github.com/tech/sendico/billing/documents/storage/model"
) )
@@ -17,7 +18,13 @@ type templateRenderer struct {
tpl *template.Template tpl *template.Template
} }
type acceptanceTemplateData struct {
model.ActSnapshot
Content content.AcceptanceTemplateContent
}
func newTemplateRenderer(path string) (*templateRenderer, error) { func newTemplateRenderer(path string) (*templateRenderer, error) {
//nolint:gosec // template file path is provided by trusted service configuration.
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("read template: %w", err) return nil, fmt.Errorf("read template: %w", err)
@@ -38,7 +45,12 @@ func newTemplateRenderer(path string) (*templateRenderer, error) {
func (r *templateRenderer) Render(snapshot model.ActSnapshot) ([]renderer.Block, error) { func (r *templateRenderer) Render(snapshot model.ActSnapshot) ([]renderer.Block, error) {
var buf bytes.Buffer var buf bytes.Buffer
if err := r.tpl.Execute(&buf, snapshot); err != nil { data := acceptanceTemplateData{
ActSnapshot: snapshot,
Content: content.AcceptanceTemplate,
}
if err := r.tpl.Execute(&buf, data); err != nil {
return nil, fmt.Errorf("execute template: %w", err) return nil, fmt.Errorf("execute template: %w", err)
} }

View File

@@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/tech/sendico/billing/documents/internal/content"
"github.com/tech/sendico/billing/documents/renderer" "github.com/tech/sendico/billing/documents/renderer"
"github.com/tech/sendico/billing/documents/storage/model" "github.com/tech/sendico/billing/documents/storage/model"
) )
@@ -42,7 +43,7 @@ func TestTemplateRenderer_Render(t *testing.T) {
t.Fatalf("expected title block") t.Fatalf("expected title block")
} }
if !slices.Contains(title.Lines, "ACT OF ACCEPTANCE OF SERVICES") { if !slices.Contains(title.Lines, content.AcceptanceTemplate.Title) {
t.Fatalf("expected title content not found") t.Fatalf("expected title content not found")
} }
@@ -54,7 +55,7 @@ func TestTemplateRenderer_Render(t *testing.T) {
foundExecutor := false foundExecutor := false
for _, row := range kv.Rows { for _, row := range kv.Rows {
if len(row) >= 2 && row[0] == "Executor" && row[1] == snapshot.ExecutorFullName { if len(row) >= 2 && row[0] == content.AcceptanceTemplate.PartyExecutorLabel && row[1] == snapshot.ExecutorFullName {
foundExecutor = true foundExecutor = true
break break

View File

@@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/jung-kurt/gofpdf" "github.com/jung-kurt/gofpdf"
"github.com/tech/sendico/billing/documents/internal/content"
) )
const ( const (
@@ -28,7 +29,7 @@ func (r Renderer) Render(blocks []Block, footerHash string) ([]byte, error) {
pdf.SetAutoPageBreak(true, pageMarginBottom) pdf.SetAutoPageBreak(true, pageMarginBottom)
pdf.SetCompression(false) pdf.SetCompression(false)
pdf.SetAuthor(r.Issuer.LegalName, false) pdf.SetAuthor(r.Issuer.LegalName, false)
pdf.SetTitle("Act of Acceptance", false) pdf.SetTitle(content.PDFTitleActOfAcceptance, false)
owner := strings.TrimSpace(r.OwnerPassword) owner := strings.TrimSpace(r.OwnerPassword)
if owner != "" { if owner != "" {
@@ -39,7 +40,7 @@ func (r Renderer) Render(blocks []Block, footerHash string) ([]byte, error) {
pdf.SetY(-15) pdf.SetY(-15)
pdf.SetFont("Helvetica", "", 8) pdf.SetFont("Helvetica", "", 8)
footer := "Document integrity hash: " + footerHash footer := content.DocumentIntegrityHashPrefix + footerHash
pdf.CellFormat(0, 5, footer, "", 0, "L", false, 0, "") pdf.CellFormat(0, 5, footer, "", 0, "L", false, 0, "")
}) })

View File

@@ -6,6 +6,8 @@ import (
"strings" "strings"
"testing" "testing"
"unicode/utf16" "unicode/utf16"
"github.com/tech/sendico/billing/documents/internal/content"
) )
func TestRenderer_RenderContainsText(t *testing.T) { func TestRenderer_RenderContainsText(t *testing.T) {
@@ -31,7 +33,7 @@ func TestRenderer_RenderContainsText(t *testing.T) {
t.Fatalf("expected PDF bytes") t.Fatalf("expected PDF bytes")
} }
checks := []string{"Sendico Ltd", "Jane Doe", "100 USD", "Document integrity hash"} checks := []string{"Sendico Ltd", "Jane Doe", "100 USD", strings.TrimSpace(strings.TrimSuffix(content.DocumentIntegrityHashPrefix, ": "))}
for _, token := range checks { for _, token := range checks {
if !containsPDFText(pdfBytes, token) { if !containsPDFText(pdfBytes, token) {
@@ -100,7 +102,7 @@ func encodeUTF16BE(text string, withBOM bool) []byte {
} }
for _, v := range encoded { for _, v := range encoded {
out = append(out, byte(v>>8), byte(v)) out = append(out, byte(v>>8), byte(v&0x00FF))
} }
return out return out

View File

@@ -6,14 +6,13 @@ import (
"github.com/shopspring/decimal" "github.com/shopspring/decimal"
"github.com/tech/sendico/pkg/db/storable" "github.com/tech/sendico/pkg/db/storable"
documentsv1 "github.com/tech/sendico/pkg/proto/billing/documents/v1"
) )
const ( const (
DocumentRecordsCollection = "document_records" DocumentRecordsCollection = "document_records"
) )
// DocumentType mirrors the protobuf enum but stores string names for Mongo compatibility. // DocumentType represents document kinds cached in storage.
type DocumentType string type DocumentType string
const ( const (
@@ -23,24 +22,6 @@ const (
DocumentTypeReceipt DocumentType = "DOCUMENT_TYPE_RECEIPT" DocumentTypeReceipt DocumentType = "DOCUMENT_TYPE_RECEIPT"
) )
// DocumentTypeFromProto converts a protobuf enum to the storage representation.
func DocumentTypeFromProto(t documentsv1.DocumentType) DocumentType {
if name, ok := documentsv1.DocumentType_name[int32(t)]; ok {
return DocumentType(name)
}
return DocumentTypeUnspecified
}
// Proto converts the storage representation to a protobuf enum.
func (t DocumentType) Proto() documentsv1.DocumentType {
if value, ok := documentsv1.DocumentType_value[string(t)]; ok {
return documentsv1.DocumentType(value)
}
return documentsv1.DocumentType_DOCUMENT_TYPE_UNSPECIFIED
}
// ActSnapshot captures the immutable data needed to generate an acceptance act. // ActSnapshot captures the immutable data needed to generate an acceptance act.
type ActSnapshot struct { type ActSnapshot struct {
PaymentID string `bson:"paymentId" json:"paymentId"` PaymentID string `bson:"paymentId" json:"paymentId"`

View File

@@ -2,66 +2,66 @@
#title #title
ACT OF ACCEPTANCE OF SERVICES {{ .Content.Title }}
#subtitle #subtitle
under the Public Offer Agreement {{ .Content.Subtitle }}
#meta #meta
Date: {{ date .Date }} {{ .Content.MetaDateLabel }}: {{ date .Date }}
Act No: {{ .PaymentID }} {{ .Content.MetaActNumberLabel }}: {{ .PaymentID }}
#section #section
PARTIES {{ .Content.SectionParties }}
#text #text
This Act is made between the following Parties. {{ .Content.PartiesIntro }}
#kv #kv
Executor | {{ .ExecutorFullName }} {{ .Content.PartyExecutorLabel }} | {{ .ExecutorFullName }}
Status | Individual {{ .Content.PartyStatusLabel }} | {{ .Content.PartyStatusValue }}
#section #section
BASIS {{ .Content.SectionBasis }}
#text #text
This Act is issued pursuant to the Public Offer Agreement {{ .Content.BasisLine1 }}
accepted by the Executor by joining the offer. {{ .Content.BasisLine2 }}
#section #section
SERVICES RENDERED {{ .Content.SectionServicesRendered }}
#text #text
The Executor has rendered services to the Customer {{ .Content.ServicesRenderedLine1 }}
in accordance with the terms of the Public Offer Agreement. {{ .Content.ServicesRenderedLine2 }}
#section #section
REMUNERATION {{ .Content.SectionRemuneration }}
#table #table
Description | Amount {{ .Content.RemunerationHeaderDesc }} | {{ .Content.RemunerationHeaderAmount }}
Services rendered under the Public Offer Agreement | {{ money .Amount .Currency }} {{ .Content.RemunerationServicesDesc }} | {{ money .Amount .Currency }}
#section #section
CONFIRMATION {{ .Content.SectionConfirmation }}
#text #text
The Customer confirms that the services were rendered properly {{ .Content.ConfirmationLine1 }}
and accepted without any claims. {{ .Content.ConfirmationLine2 }}
The remuneration for the services was paid to the Executor {{ .Content.ConfirmationPaymentLine1 }}
using the bank card details provided by the Executor. {{ .Content.ConfirmationPaymentLine2 }}
#section #section
SIGNATURES {{ .Content.SectionSignatures }}
#sign #sign
Customer ___________________________ {{ .Content.SignatureCustomerLine }}
Executor ___________________________ {{ .Content.SignatureExecutorLine }}

View File

@@ -1,198 +1,47 @@
# See the dedicated "version" documentation section.
version: "2" version: "2"
linters: linters:
# Default set of linters. default: none
# The value can be:
# - `standard`: https://golangci-lint.run/docs/linters/#enabled-by-default
# - `all`: enables all linters by default.
# - `none`: disables all linters by default.
# - `fast`: enables only linters considered as "fast" (`golangci-lint help linters --json | jq '[ .[] | select(.fast==true) ] | map(.name)'`).
# Default: standard
default: all
# Enable specific linter.
enable: enable:
- arangolint
- asasalint
- asciicheck
- bidichk
- bodyclose - bodyclose
- canonicalheader - canonicalheader
- containedctx
- contextcheck
- copyloopvar - copyloopvar
- cyclop
- decorder
- dogsled
- dupl
- dupword
- durationcheck - durationcheck
- embeddedstructfieldcheck
- err113
- errcheck - errcheck
- errchkjson - errchkjson
- errname - errname
- errorlint - errorlint
- exhaustive
- exptostd
- fatcontext
- forbidigo
- forcetypeassert
- funcorder
- funlen
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gochecksumtype
- gocognit
- goconst
- gocritic
- gocyclo
- godoclint
- godot
- godox
- goheader
- gomodguard
- goprintffuncname
- gosec - gosec
- gosmopolitan
- govet - govet
- grouper
- iface
- importas
- inamedparam
- ineffassign - ineffassign
- interfacebloat
- intrange
- iotamixing
- ireturn
- lll
- loggercheck
- maintidx
- makezero
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
- nilerr - nilerr
- nilnesserr - nilnesserr
- nilnil - nilnil
- nlreturn
- noctx - noctx
- noinlineerr
- nolintlint
- nonamedreturns
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- recvcheck
- revive
- rowserrcheck - rowserrcheck
- sloglint
- spancheck
- sqlclosecheck - sqlclosecheck
- staticcheck - staticcheck
- tagalign
- tagliatelle
- testableexamples
- testifylint
- testpackage
- thelper
- tparallel
- unconvert - unconvert
- unparam
- unqueryvet
- unused
- usestdlibvars
- usetesting
- varnamelen
- wastedassign - wastedassign
- whitespace
- wsl_v5
- zerologlint
# Disable specific linters.
disable: disable:
- depguard - depguard
- exhaustruct - exhaustruct
- gochecknoglobals - gochecknoglobals
- gochecknoinits
- gomoddirectives - gomoddirectives
- noinlineerr
- wsl
- wrapcheck - wrapcheck
# All available settings of specific linters. - cyclop
# See the dedicated "linters.settings" documentation section. - dupl
settings: - funlen
wsl_v5: - gocognit
allow-first-in-block: true - gocyclo
allow-whole-block: false - ireturn
branch-max-lines: 2 - lll
- mnd
# Defines a set of rules to ignore issues. - nestif
# It does not skip the analysis, and so does not ignore "typecheck" errors. - nlreturn
exclusions: - noinlineerr
# Mode of the generated files analysis. - paralleltest
# - tagliatelle
# - `strict`: sources are excluded by strictly following the Go generated file convention. - testpackage
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$` - varnamelen
# This line must appear before the first non-comment, non-blank text in the file. - wsl_v5
# https://go.dev/s/generatedcode
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
# - `disable`: disable the generated files exclusion.
#
# Default: strict
generated: lax
# Log a warning if an exclusion rule is unused.
# Default: false
warn-unused: true
# Predefined exclusion rules.
# Default: []
presets:
- comments
- std-error-handling
- common-false-positives
- legacy
# Excluding configuration per-path, per-linter, per-text and per-source.
rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- cyclop
- funlen
- gocyclo
- errcheck
- dupl
- gosec
# Run some linter only for test files by excluding its issues for everything else.
- path-except: _test\.go
linters:
- forbidigo
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via `nolint` comments.
# `/` will be replaced by the current OS file path separator to properly work on Windows.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
# Exclude some `staticcheck` messages.
- linters:
- staticcheck
text: "SA9003:"
# Exclude `lll` issues for long lines with `go:generate`.
- linters:
- lll
source: "^//go:generate "
# Which file paths to exclude: they will be analyzed, but issues from them won't be reported.
# "/" will be replaced by the current OS file path separator to properly work on Windows.
# Default: []
paths: []
# Which file paths to not exclude.
# Default: []
paths-except: []

View File

@@ -10,7 +10,7 @@ require (
github.com/tech/sendico/fx/oracle v0.0.0 github.com/tech/sendico/fx/oracle v0.0.0
github.com/tech/sendico/pkg v0.1.0 github.com/tech/sendico/pkg v0.1.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
google.golang.org/grpc v1.79.1 google.golang.org/grpc v1.79.2
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -44,11 +44,11 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11

View File

@@ -168,8 +168,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -183,16 +183,16 @@ golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210615035016-665e8c7367d1/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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -1,196 +1,47 @@
# See the dedicated "version" documentation section.
version: "2" version: "2"
linters: linters:
# Default set of linters. default: none
# The value can be:
# - `standard`: https://golangci-lint.run/docs/linters/#enabled-by-default
# - `all`: enables all linters by default.
# - `none`: disables all linters by default.
# - `fast`: enables only linters considered as "fast" (`golangci-lint help linters --json | jq '[ .[] | select(.fast==true) ] | map(.name)'`).
# Default: standard
default: all
# Enable specific linter.
enable: enable:
- arangolint
- asasalint
- asciicheck
- bidichk
- bodyclose - bodyclose
- canonicalheader - canonicalheader
- containedctx
- contextcheck
- copyloopvar - copyloopvar
- cyclop
- decorder
- dogsled
- dupl
- dupword
- durationcheck - durationcheck
- embeddedstructfieldcheck
- err113
- errcheck - errcheck
- errchkjson - errchkjson
- errname - errname
- errorlint - errorlint
- exhaustive
- exptostd
- fatcontext
- forbidigo
- forcetypeassert
- funcorder
- funlen
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gochecksumtype
- gocognit
- goconst
- gocritic
- gocyclo
- godoclint
- godot
- godox
- goheader
- gomodguard
- goprintffuncname
- gosec - gosec
- gosmopolitan
- govet - govet
- grouper
- iface
- importas
- inamedparam
- ineffassign - ineffassign
- interfacebloat
- intrange
- iotamixing
- ireturn
- lll
- loggercheck
- maintidx
- makezero
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
- nilerr - nilerr
- nilnesserr - nilnesserr
- nilnil - nilnil
- nlreturn
- noctx - noctx
- noinlineerr
- nolintlint
- nonamedreturns
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- recvcheck
- revive
- rowserrcheck - rowserrcheck
- sloglint
- spancheck
- sqlclosecheck - sqlclosecheck
- staticcheck - staticcheck
- tagalign
- tagliatelle
- testableexamples
- testifylint
- testpackage
- thelper
- tparallel
- unconvert - unconvert
- unparam
- unqueryvet
- unused
- usestdlibvars
- usetesting
- varnamelen
- wastedassign - wastedassign
- whitespace
- wsl_v5
- zerologlint
# Disable specific linters.
disable: disable:
- depguard - depguard
- exhaustruct - exhaustruct
- gochecknoglobals - gochecknoglobals
- gochecknoinits
- gomoddirectives - gomoddirectives
- wsl
- wrapcheck - wrapcheck
# All available settings of specific linters. - cyclop
# See the dedicated "linters.settings" documentation section. - dupl
settings: - funlen
wsl_v5: - gocognit
allow-first-in-block: true - gocyclo
allow-whole-block: false - ireturn
branch-max-lines: 2 - lll
- mnd
# Defines a set of rules to ignore issues. - nestif
# It does not skip the analysis, and so does not ignore "typecheck" errors. - nlreturn
exclusions: - noinlineerr
# Mode of the generated files analysis. - paralleltest
# - tagliatelle
# - `strict`: sources are excluded by strictly following the Go generated file convention. - testpackage
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$` - varnamelen
# This line must appear before the first non-comment, non-blank text in the file. - wsl_v5
# https://go.dev/s/generatedcode
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
# - `disable`: disable the generated files exclusion.
#
# Default: strict
generated: lax
# Log a warning if an exclusion rule is unused.
# Default: false
warn-unused: true
# Predefined exclusion rules.
# Default: []
presets:
- comments
- std-error-handling
- common-false-positives
- legacy
# Excluding configuration per-path, per-linter, per-text and per-source.
rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- funlen
- gocyclo
- errcheck
- dupl
- gosec
# Run some linter only for test files by excluding its issues for everything else.
- path-except: _test\.go
linters:
- forbidigo
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via `nolint` comments.
# `/` will be replaced by the current OS file path separator to properly work on Windows.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
# Exclude some `staticcheck` messages.
- linters:
- staticcheck
text: "SA9003:"
# Exclude `lll` issues for long lines with `go:generate`.
- linters:
- lll
source: "^//go:generate "
# Which file paths to exclude: they will be analyzed, but issues from them won't be reported.
# "/" will be replaced by the current OS file path separator to properly work on Windows.
# Default: []
paths: []
# Which file paths to not exclude.
# Default: []
paths-except: []

View File

@@ -37,13 +37,13 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
) )

View File

@@ -168,8 +168,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -183,16 +183,16 @@ golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210615035016-665e8c7367d1/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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,47 @@
version: "2"
linters:
default: none
enable:
- bodyclose
- canonicalheader
- copyloopvar
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- gosec
- govet
- ineffassign
- nilerr
- nilnesserr
- nilnil
- noctx
- rowserrcheck
- sqlclosecheck
- staticcheck
- unconvert
- wastedassign
disable:
- depguard
- exhaustruct
- gochecknoglobals
- gochecknoinits
- gomoddirectives
- wrapcheck
- cyclop
- dupl
- funlen
- gocognit
- gocyclo
- ireturn
- lll
- mnd
- nestif
- nlreturn
- noinlineerr
- paralleltest
- tagliatelle
- testpackage
- varnamelen
- wsl_v5

View File

@@ -18,7 +18,7 @@ require (
github.com/aws/aws-sdk-go-v2 v1.41.3 github.com/aws/aws-sdk-go-v2 v1.41.3
github.com/aws/aws-sdk-go-v2/config v1.32.11 github.com/aws/aws-sdk-go-v2/config v1.32.11
github.com/aws/aws-sdk-go-v2/credentials v1.19.11 github.com/aws/aws-sdk-go-v2/credentials v1.19.11
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4
github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/go-chi/jwtauth/v5 v5.4.0 github.com/go-chi/jwtauth/v5 v5.4.0
@@ -38,7 +38,7 @@ require (
go.mongodb.org/mongo-driver/v2 v2.5.0 go.mongodb.org/mongo-driver/v2 v2.5.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
golang.org/x/net v0.51.0 golang.org/x/net v0.51.0
google.golang.org/grpc v1.79.1 google.golang.org/grpc v1.79.2
google.golang.org/protobuf v1.36.11 google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
moul.io/chizap v1.0.3 moul.io/chizap v1.0.3
@@ -59,7 +59,7 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 // indirect
@@ -147,17 +147,17 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
go.opentelemetry.io/otel v1.41.0 // indirect go.opentelemetry.io/otel v1.42.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.41.0 // indirect go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.41.0 // indirect go.opentelemetry.io/otel/trace v1.42.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
) )

View File

@@ -22,8 +22,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19 h1:AWeJMk33GTBf6J20XJ
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.19/go.mod h1:+GWrYoaAsV7/4pNHpwh1kiNLXkKaSoppxQq9lbH8Ejw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5 h1:clHU5fm//kWS1C2HgtgWxfQbFbx4b6rx+5jzhgX9HrI=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.5/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19 h1:3Y4oma5TiV7tT9wa8zRcdoXwZkGz9Q/wxbEUK7cMuAM= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20 h1:qi3e/dmpdONhj1RyIZdi6DKKpDXS5Lb8ftr3p7cyHJc=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.19/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c= github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.20/go.mod h1:V1K+TeJVD5JOk3D9e5tsX2KUdL7BlB+FV6cBhdobN8c=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6 h1:XAq62tBTJP/85lFD5oqOOe7YYgWxY9LvWq8plyDvDVg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.6/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw= github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.11 h1:BYf7XNsJMzl4mObARUBUib+j2tf0U//JAAtTnYqvqCw=
@@ -32,8 +32,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19 h1:X1Tow7su
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.19/go.mod h1:/rARO8psX+4sfjUQXp5LLifjUt8DuATZ31WptNJTyQA=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19 h1:JnQeStZvPHFHeyky/7LbMlyQjUa+jIBj36OlWm0pzIk=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.19/go.mod h1:HGyasyHvYdFQeJhvDHfH7HXkHh57htcJGKDZ+7z+I24=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3 h1:+d0SsTvxtIJt4tSJ6wr+jrxEMDa6XeupjRv8H7Qitkk= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4 h1:4ExZyubQ6LQQVuF2Qp9OsfEvsTdAWh5Gfwf6PgIdLdk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.96.3/go.mod h1:ROUNFvFWPwBlOu687WJNQ9cPvd2ccpFrnCiA1YGz50o= github.com/aws/aws-sdk-go-v2/service/s3 v1.96.4/go.mod h1:NF3JcMGOiARAss1ld3WGORCw71+4ExDD2cbbdKS5PpA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7 h1:Y2cAXlClHsXkkOvWZFXATr34b0hxxloeQu/pAZz2row=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.7/go.mod h1:idzZ7gmDeqeNrSPkdbtMp9qWMgcBwykA7P7Rzh5DXVU=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias= github.com/aws/aws-sdk-go-v2/service/sso v1.30.12 h1:iSsvB9EtQ09YrsmIc44Heqlx5ByGErqhPK1ZQLppias=
@@ -296,20 +296,20 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4=
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY=
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@@ -324,8 +324,8 @@ go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN8
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -353,8 +353,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -370,8 +370,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
@@ -382,8 +382,8 @@ 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
@@ -403,8 +403,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171 h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng= google.golang.org/genproto/googleapis/api v0.0.0-20260226221140-a57be14db171/go.mod h1:M5krXqk4GhBKvB596udGL3UyjL4I1+cTbK0orROM9ng=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -159,7 +159,7 @@ func (s *service) VerifyAccount(
token, err := s.vdb.Create( token, err := s.vdb.Create(
ctx, ctx,
verification.NewLinkRequest(*acct.GetID(), model.PurposeAccountActivation, ""). verification.NewLinkRequest(*acct.GetID(), model.PurposeAccountActivation, "").
WithTTL(time.Duration(time.Hour*24)), WithTTL(time.Hour * 24),
) )
if err != nil { if err != nil {
s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct)) s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct))
@@ -238,7 +238,7 @@ func (s *service) ResetPassword(
return s.vdb.Create( return s.vdb.Create(
ctx, ctx,
verification.NewOTPRequest(*acct.GetID(), model.PurposePasswordReset, ""). verification.NewOTPRequest(*acct.GetID(), model.PurposePasswordReset, "").
WithTTL(time.Duration(time.Hour*1)), WithTTL(time.Hour),
) )
} }
@@ -250,7 +250,7 @@ func (s *service) UpdateLogin(
return s.vdb.Create( return s.vdb.Create(
ctx, ctx,
verification.NewOTPRequest(*acct.GetID(), model.PurposeEmailChange, newLogin). verification.NewOTPRequest(*acct.GetID(), model.PurposeEmailChange, newLogin).
WithTTL(time.Duration(time.Hour*1)), WithTTL(time.Hour),
) )
} }

View File

@@ -164,6 +164,7 @@ func (e Endpoint) DecodeIBAN() (IBANEndpoint, error) {
func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint, error) { func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint, error) {
if old == nil { if old == nil {
//nolint:nilnil // Nil legacy endpoint means no endpoint provided.
return nil, nil return nil, nil
} }
@@ -202,6 +203,7 @@ func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint,
func EndpointDTOToLegacyPaymentEndpoint(new *Endpoint) (*LegacyPaymentEndpoint, error) { func EndpointDTOToLegacyPaymentEndpoint(new *Endpoint) (*LegacyPaymentEndpoint, error) {
if new == nil { if new == nil {
//nolint:nilnil // Nil endpoint DTO means no endpoint provided.
return nil, nil return nil, nil
} }

View File

@@ -14,6 +14,7 @@ type PaymentIntent struct {
SettlementMode SettlementMode `json:"settlement_mode,omitempty"` SettlementMode SettlementMode `json:"settlement_mode,omitempty"`
FeeTreatment FeeTreatment `json:"fee_treatment,omitempty"` FeeTreatment FeeTreatment `json:"fee_treatment,omitempty"`
Attributes map[string]string `json:"attributes,omitempty"` Attributes map[string]string `json:"attributes,omitempty"`
Comment string `json:"comment,omitempty"`
Customer *Customer `json:"customer,omitempty"` Customer *Customer `json:"customer,omitempty"`
} }

View File

@@ -14,8 +14,11 @@ import (
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1" gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1" paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1" oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2" orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2" quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
"github.com/tech/sendico/server/interface/api/srequest"
"go.mongodb.org/mongo-driver/v2/bson"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
@@ -67,15 +70,24 @@ type PaymentQuotes struct {
} }
type Payment struct { type Payment struct {
PaymentRef string `json:"paymentRef,omitempty"` PaymentRef string `json:"paymentRef,omitempty"`
IdempotencyKey string `json:"idempotencyKey,omitempty"` State string `json:"state,omitempty"`
State string `json:"state,omitempty"` Comment string `json:"comment,omitempty"`
FailureCode string `json:"failureCode,omitempty"` Source *PaymentEndpoint `json:"source"`
FailureReason string `json:"failureReason,omitempty"` Destination *PaymentEndpoint `json:"destination"`
Operations []PaymentOperation `json:"operations,omitempty"` FailureCode string `json:"failureCode,omitempty"`
LastQuote *PaymentQuote `json:"lastQuote,omitempty"` FailureReason string `json:"failureReason,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"` Operations []PaymentOperation `json:"operations,omitempty"`
Meta map[string]string `json:"meta,omitempty"` LastQuote *PaymentQuote `json:"lastQuote,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
Meta map[string]string `json:"meta,omitempty"`
}
type PaymentEndpoint struct {
Type string `json:"type,omitempty"`
Data any `json:"data,omitempty"`
PaymentMethodRef string `json:"paymentMethodRef,omitempty"`
PayeeRef string `json:"payeeRef,omitempty"`
} }
type PaymentOperation struct { type PaymentOperation struct {
@@ -289,21 +301,257 @@ func toPayment(p *orchestrationv2.Payment) *Payment {
if p == nil { if p == nil {
return nil return nil
} }
operations := toUserVisibleOperations(p.GetStepExecutions(), p.GetQuoteSnapshot()) intent := p.GetIntentSnapshot()
operations := toUserVisibleOperations(p.GetStepExecutions())
failureCode, failureReason := firstFailure(operations) failureCode, failureReason := firstFailure(operations)
return &Payment{ return &Payment{
PaymentRef: p.GetPaymentRef(), PaymentRef: p.GetPaymentRef(),
State: enumJSONName(p.GetState().String()), State: enumJSONName(p.GetState().String()),
FailureCode: failureCode, Comment: strings.TrimSpace(intent.GetComment()),
FailureReason: failureReason, Source: toPaymentEndpoint(intent.GetSource()),
Operations: operations, Destination: toPaymentEndpoint(intent.GetDestination()),
LastQuote: toPaymentQuote(p.GetQuoteSnapshot()), FailureCode: failureCode,
CreatedAt: timestampAsTime(p.GetCreatedAt()), FailureReason: failureReason,
Meta: paymentMeta(p), Operations: operations,
IdempotencyKey: "", LastQuote: toPaymentQuote(p.GetQuoteSnapshot()),
CreatedAt: timestampAsTime(p.GetCreatedAt()),
Meta: paymentMeta(p),
} }
} }
func toPaymentEndpoint(endpoint *endpointv1.PaymentEndpoint) *PaymentEndpoint {
if endpoint == nil {
return nil
}
if paymentMethodRef := strings.TrimSpace(endpoint.GetPaymentMethodRef()); paymentMethodRef != "" {
return &PaymentEndpoint{PaymentMethodRef: paymentMethodRef}
}
if payeeRef := strings.TrimSpace(endpoint.GetPayeeRef()); payeeRef != "" {
return &PaymentEndpoint{PayeeRef: payeeRef}
}
method := endpoint.GetPaymentMethod()
if method == nil {
return nil
}
return &PaymentEndpoint{
Type: paymentEndpointType(method.GetType()),
Data: paymentEndpointData(method),
}
}
func paymentEndpointType(methodType endpointv1.PaymentMethodType) string {
switch methodType {
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN:
return string(srequest.EndpointTypeIBAN)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD:
return string(srequest.EndpointTypeCard)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN:
return string(srequest.EndpointTypeCardToken)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT:
return string(srequest.EndpointTypeBankAccount)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET:
return string(srequest.EndpointTypeWallet)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS:
return string(srequest.EndpointTypeExternalChain)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER:
return string(srequest.EndpointTypeLedger)
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_ACCOUNT:
return "account"
default:
return "unspecified"
}
}
func paymentEndpointData(method *endpointv1.PaymentMethod) any {
if method == nil {
return nil
}
switch method.GetType() {
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_LEDGER:
type ledgerMethodData struct {
LedgerAccountRef string `bson:"ledgerAccountRef"`
ContraLedgerAccountRef string `bson:"contraLedgerAccountRef,omitempty"`
}
var payload ledgerMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
return srequest.LedgerEndpoint{
LedgerAccountRef: strings.TrimSpace(payload.LedgerAccountRef),
ContraLedgerAccountRef: strings.TrimSpace(payload.ContraLedgerAccountRef),
}
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET:
type walletMethodData struct {
WalletID string `bson:"walletId"`
}
var payload walletMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
return srequest.WalletEndpoint{
WalletID: strings.TrimSpace(payload.WalletID),
}
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS:
type cryptoMethodData struct {
Currency string `bson:"currency"`
Address string `bson:"address"`
Network string `bson:"network"`
DestinationTag *string `bson:"destinationTag,omitempty"`
}
var payload cryptoMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
endpoint := srequest.ExternalChainEndpoint{
Asset: &srequest.Asset{
Chain: parseChainNetwork(payload.Network),
TokenSymbol: strings.ToUpper(strings.TrimSpace(payload.Currency)),
},
Address: strings.TrimSpace(payload.Address),
}
if memo := strings.TrimSpace(strPtr(payload.DestinationTag)); memo != "" {
endpoint.Memo = memo
}
if endpoint.Asset.Chain == srequest.ChainNetworkUnspecified && endpoint.Asset.TokenSymbol == "" {
endpoint.Asset = nil
}
return endpoint
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD:
type cardMethodData struct {
Pan string `bson:"pan"`
FirstName string `bson:"firstName"`
LastName string `bson:"lastName"`
ExpMonth string `bson:"expMonth"`
ExpYear string `bson:"expYear"`
Country string `bson:"country,omitempty"`
}
var payload cardMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
return srequest.CardEndpoint{
Pan: strings.TrimSpace(payload.Pan),
FirstName: strings.TrimSpace(payload.FirstName),
LastName: strings.TrimSpace(payload.LastName),
ExpMonth: parseUint32(payload.ExpMonth),
ExpYear: parseUint32(payload.ExpYear),
Country: strings.TrimSpace(payload.Country),
}
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CARD_TOKEN:
type cardTokenMethodData struct {
Token string `bson:"token"`
Last4 string `bson:"last4,omitempty"`
}
var payload cardTokenMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
return srequest.CardTokenEndpoint{
Token: strings.TrimSpace(payload.Token),
MaskedPan: strings.TrimSpace(payload.Last4),
}
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_BANK_ACCOUNT:
type bankAccountMethodData struct {
RecipientName string `bson:"recipientName"`
Inn string `bson:"inn"`
Kpp string `bson:"kpp"`
BankName string `bson:"bankName"`
Bik string `bson:"bik"`
AccountNumber string `bson:"accountNumber"`
CorrespondentAccount string `bson:"correspondentAccount"`
}
var payload bankAccountMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
return srequest.BankAccountEndpoint{
RecipientName: strings.TrimSpace(payload.RecipientName),
Inn: strings.TrimSpace(payload.Inn),
Kpp: strings.TrimSpace(payload.Kpp),
BankName: strings.TrimSpace(payload.BankName),
Bik: strings.TrimSpace(payload.Bik),
AccountNumber: strings.TrimSpace(payload.AccountNumber),
CorrespondentAccount: strings.TrimSpace(payload.CorrespondentAccount),
}
case endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_IBAN:
type ibanMethodData struct {
IBAN string `bson:"iban"`
AccountHolder string `bson:"accountHolder"`
BIC *string `bson:"bic,omitempty"`
BankName *string `bson:"bankName,omitempty"`
}
var payload ibanMethodData
if err := bson.Unmarshal(method.GetData(), &payload); err != nil {
return toRawBSON(method.GetData())
}
return srequest.IBANEndpoint{
IBAN: strings.TrimSpace(payload.IBAN),
AccountHolder: strings.TrimSpace(payload.AccountHolder),
BIC: strings.TrimSpace(strPtr(payload.BIC)),
BankName: strings.TrimSpace(strPtr(payload.BankName)),
}
default:
return toRawBSON(method.GetData())
}
}
func toRawBSON(raw []byte) map[string]any {
if len(raw) == 0 {
return nil
}
var data map[string]any
if err := bson.Unmarshal(raw, &data); err != nil {
return nil
}
if len(data) == 0 {
return nil
}
return data
}
func parseChainNetwork(value string) srequest.ChainNetwork {
switch strings.ToUpper(strings.TrimSpace(value)) {
case "ETHEREUM_MAINNET":
return srequest.ChainNetworkEthereumMainnet
case "ARBITRUM_ONE":
return srequest.ChainNetworkArbitrumOne
case "TRON_MAINNET":
return srequest.ChainNetworkTronMainnet
case "TRON_NILE":
return srequest.ChainNetworkTronNile
case "", "UNSPECIFIED":
return srequest.ChainNetworkUnspecified
default:
return srequest.ChainNetwork(strings.ToLower(strings.TrimSpace(value)))
}
}
func parseUint32(value string) uint32 {
clean := strings.TrimSpace(value)
if clean == "" {
return 0
}
parsed, err := strconv.ParseUint(clean, 10, 32)
if err != nil {
return 0
}
return uint32(parsed)
}
func strPtr(v *string) string {
if v == nil {
return ""
}
return *v
}
func firstFailure(operations []PaymentOperation) (string, string) { func firstFailure(operations []PaymentOperation) (string, string) {
for _, op := range operations { for _, op := range operations {
if strings.TrimSpace(op.FailureCode) == "" && strings.TrimSpace(op.FailureReason) == "" { if strings.TrimSpace(op.FailureCode) == "" && strings.TrimSpace(op.FailureReason) == "" {
@@ -314,7 +562,7 @@ func firstFailure(operations []PaymentOperation) (string, string) {
return "", "" return "", ""
} }
func toUserVisibleOperations(steps []*orchestrationv2.StepExecution, quote *quotationv2.PaymentQuote) []PaymentOperation { func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOperation {
if len(steps) == 0 { if len(steps) == 0 {
return nil return nil
} }
@@ -323,7 +571,7 @@ func toUserVisibleOperations(steps []*orchestrationv2.StepExecution, quote *quot
if step == nil || !isUserVisibleStep(step.GetReportVisibility()) { if step == nil || !isUserVisibleStep(step.GetReportVisibility()) {
continue continue
} }
ops = append(ops, toPaymentOperation(step, quote)) ops = append(ops, toPaymentOperation(step))
} }
if len(ops) == 0 { if len(ops) == 0 {
return nil return nil
@@ -331,9 +579,10 @@ func toUserVisibleOperations(steps []*orchestrationv2.StepExecution, quote *quot
return ops return ops
} }
func toPaymentOperation(step *orchestrationv2.StepExecution, quote *quotationv2.PaymentQuote) PaymentOperation { func toPaymentOperation(step *orchestrationv2.StepExecution) PaymentOperation {
operationRef, gateway := operationRefAndGateway(step.GetStepCode(), step.GetRefs()) operationRef, gateway := operationRefAndGateway(step.GetStepCode(), step.GetRefs())
amount, convertedAmount := operationAmounts(step.GetStepCode(), quote) amount := normalizeOperationMoney(toMoney(step.GetExecutedMoney()))
convertedAmount := normalizeOperationMoney(toMoney(step.GetConvertedMoney()))
op := PaymentOperation{ op := PaymentOperation{
StepRef: step.GetStepRef(), StepRef: step.GetStepRef(),
Code: step.GetStepCode(), Code: step.GetStepCode(),
@@ -342,7 +591,7 @@ func toPaymentOperation(step *orchestrationv2.StepExecution, quote *quotationv2.
Amount: amount, Amount: amount,
ConvertedAmount: convertedAmount, ConvertedAmount: convertedAmount,
OperationRef: operationRef, OperationRef: operationRef,
Gateway: string(gateway), Gateway: gateway,
StartedAt: timestampAsTime(step.GetStartedAt()), StartedAt: timestampAsTime(step.GetStartedAt()),
CompletedAt: timestampAsTime(step.GetCompletedAt()), CompletedAt: timestampAsTime(step.GetCompletedAt()),
} }
@@ -358,52 +607,19 @@ func toPaymentOperation(step *orchestrationv2.StepExecution, quote *quotationv2.
return op return op
} }
func operationAmounts(stepCode string, quote *quotationv2.PaymentQuote) (*paymenttypes.Money, *paymenttypes.Money) { func normalizeOperationMoney(value *paymenttypes.Money) *paymenttypes.Money {
if quote == nil { if value == nil {
return nil, nil return nil
} }
operation := stepOperationToken(stepCode) amount := strings.TrimSpace(value.GetAmount())
currency := strings.TrimSpace(value.GetCurrency())
primary := firstValidMoney( if amount == "" || currency == "" {
toMoney(quote.GetDestinationAmount()), return nil
toMoney(quote.GetTransferPrincipalAmount()),
toMoney(quote.GetPayerTotalDebitAmount()),
)
if operation != "fx_convert" {
return primary, nil
} }
return &paymenttypes.Money{
base := firstValidMoney( Amount: amount,
toMoney(quote.GetTransferPrincipalAmount()), Currency: currency,
toMoney(quote.GetPayerTotalDebitAmount()),
toMoney(quote.GetFxQuote().GetBaseAmount()),
)
quoteAmount := firstValidMoney(
toMoney(quote.GetDestinationAmount()),
toMoney(quote.GetFxQuote().GetQuoteAmount()),
)
return base, quoteAmount
}
func stepOperationToken(stepCode string) string {
parts := strings.Split(strings.ToLower(strings.TrimSpace(stepCode)), ".")
if len(parts) == 0 {
return ""
} }
return strings.TrimSpace(parts[len(parts)-1])
}
func firstValidMoney(values ...*paymenttypes.Money) *paymenttypes.Money {
for _, value := range values {
if value == nil {
continue
}
if strings.TrimSpace(value.GetAmount()) == "" || strings.TrimSpace(value.GetCurrency()) == "" {
continue
}
return value
}
return nil
} }
const ( const (
@@ -454,9 +670,9 @@ func gatewayTypeFromInstanceID(raw string) mservice.Type {
return "" return ""
} }
switch mservice.Type(value) { switch value {
case mservice.ChainGateway, mservice.TronGateway, mservice.MntxGateway, mservice.PaymentGateway, mservice.TgSettle, mservice.Ledger: case mservice.ChainGateway, mservice.TronGateway, mservice.MntxGateway, mservice.PaymentGateway, mservice.TgSettle, mservice.Ledger:
return mservice.Type(value) return value
} }
switch { switch {

View File

@@ -5,9 +5,12 @@ import (
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1" gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1" moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
endpointv1 "github.com/tech/sendico/pkg/proto/payments/endpoint/v1"
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2" orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2" quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1" sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
"github.com/tech/sendico/server/interface/api/srequest"
"go.mongodb.org/mongo-driver/v2/bson"
) )
func TestToUserVisibleOperationsFiltersByVisibility(t *testing.T) { func TestToUserVisibleOperationsFiltersByVisibility(t *testing.T) {
@@ -34,7 +37,7 @@ func TestToUserVisibleOperationsFiltersByVisibility(t *testing.T) {
}, },
} }
ops := toUserVisibleOperations(steps, nil) ops := toUserVisibleOperations(steps)
if len(ops) != 2 { if len(ops) != 2 {
t.Fatalf("operations count mismatch: got=%d want=2", len(ops)) t.Fatalf("operations count mismatch: got=%d want=2", len(ops))
} }
@@ -121,6 +124,141 @@ func TestToPaymentIgnoresHiddenFailures(t *testing.T) {
} }
} }
func TestToPaymentMapsIntentComment(t *testing.T) {
dto := toPayment(&orchestrationv2.Payment{
PaymentRef: "pay-3",
State: orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_CREATED,
IntentSnapshot: &quotationv2.QuoteIntent{
Comment: " invoice-7 ",
},
})
if dto == nil {
t.Fatal("expected non-nil payment dto")
}
if got, want := dto.Comment, "invoice-7"; got != want {
t.Fatalf("comment mismatch: got=%q want=%q", got, want)
}
}
func TestToPaymentMapsSourceAndDestination(t *testing.T) {
sourceRaw, err := bson.Marshal(struct {
WalletID string `bson:"walletId"`
}{
WalletID: "wallet-src-1",
})
if err != nil {
t.Fatalf("marshal source method data: %v", err)
}
destinationRaw, err := bson.Marshal(struct {
Currency string `bson:"currency"`
Address string `bson:"address"`
Network string `bson:"network"`
DestinationTag *string `bson:"destinationTag,omitempty"`
}{
Currency: "USDT",
Address: "TXabc",
Network: "TRON_MAINNET",
})
if err != nil {
t.Fatalf("marshal destination method data: %v", err)
}
dto := toPayment(&orchestrationv2.Payment{
PaymentRef: "pay-src-dst",
State: orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_CREATED,
IntentSnapshot: &quotationv2.QuoteIntent{
Source: &endpointv1.PaymentEndpoint{
Source: &endpointv1.PaymentEndpoint_PaymentMethod{
PaymentMethod: &endpointv1.PaymentMethod{
Type: endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_WALLET,
Data: sourceRaw,
},
},
},
Destination: &endpointv1.PaymentEndpoint{
Source: &endpointv1.PaymentEndpoint_PaymentMethod{
PaymentMethod: &endpointv1.PaymentMethod{
Type: endpointv1.PaymentMethodType_PAYMENT_METHOD_TYPE_CRYPTO_ADDRESS,
Data: destinationRaw,
},
},
},
},
})
if dto == nil {
t.Fatal("expected non-nil payment dto")
}
if dto.Source == nil {
t.Fatal("expected source endpoint")
}
if got, want := dto.Source.Type, string(srequest.EndpointTypeWallet); got != want {
t.Fatalf("source type mismatch: got=%q want=%q", got, want)
}
sourceEndpoint, ok := dto.Source.Data.(srequest.WalletEndpoint)
if !ok {
t.Fatalf("source endpoint payload type mismatch: got=%T", dto.Source.Data)
}
if got, want := sourceEndpoint.WalletID, "wallet-src-1"; got != want {
t.Fatalf("source wallet id mismatch: got=%q want=%q", got, want)
}
if dto.Destination == nil {
t.Fatal("expected destination endpoint")
}
if got, want := dto.Destination.Type, string(srequest.EndpointTypeExternalChain); got != want {
t.Fatalf("destination type mismatch: got=%q want=%q", got, want)
}
destinationEndpoint, ok := dto.Destination.Data.(srequest.ExternalChainEndpoint)
if !ok {
t.Fatalf("destination endpoint payload type mismatch: got=%T", dto.Destination.Data)
}
if got, want := destinationEndpoint.Address, "TXabc"; got != want {
t.Fatalf("destination address mismatch: got=%q want=%q", got, want)
}
if destinationEndpoint.Asset == nil {
t.Fatal("expected destination asset")
}
if got, want := destinationEndpoint.Asset.TokenSymbol, "USDT"; got != want {
t.Fatalf("destination token mismatch: got=%q want=%q", got, want)
}
if got, want := destinationEndpoint.Asset.Chain, srequest.ChainNetworkTronMainnet; got != want {
t.Fatalf("destination chain mismatch: got=%q want=%q", got, want)
}
}
func TestToPaymentMapsEndpointRefs(t *testing.T) {
dto := toPayment(&orchestrationv2.Payment{
PaymentRef: "pay-refs",
State: orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_CREATED,
IntentSnapshot: &quotationv2.QuoteIntent{
Source: &endpointv1.PaymentEndpoint{
Source: &endpointv1.PaymentEndpoint_PaymentMethodRef{
PaymentMethodRef: "pm-123",
},
},
Destination: &endpointv1.PaymentEndpoint{
Source: &endpointv1.PaymentEndpoint_PayeeRef{
PayeeRef: "payee-777",
},
},
},
})
if dto == nil {
t.Fatal("expected non-nil payment dto")
}
if dto.Source == nil {
t.Fatal("expected source endpoint")
}
if got, want := dto.Source.PaymentMethodRef, "pm-123"; got != want {
t.Fatalf("source payment_method_ref mismatch: got=%q want=%q", got, want)
}
if dto.Destination == nil {
t.Fatal("expected destination endpoint")
}
if got, want := dto.Destination.PayeeRef, "payee-777"; got != want {
t.Fatalf("destination payee_ref mismatch: got=%q want=%q", got, want)
}
}
func TestToPaymentQuote_MapsIntentRef(t *testing.T) { func TestToPaymentQuote_MapsIntentRef(t *testing.T) {
dto := toPaymentQuote(&quotationv2.PaymentQuote{ dto := toPaymentQuote(&quotationv2.PaymentQuote{
QuoteRef: "quote-1", QuoteRef: "quote-1",
@@ -150,7 +288,7 @@ func TestToPaymentOperation_MapsOperationRefAndGateway(t *testing.T) {
Ref: "op-123", Ref: "op-123",
}, },
}, },
}, nil) })
if got, want := op.OperationRef, "op-123"; got != want { if got, want := op.OperationRef, "op-123"; got != want {
t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want) t.Fatalf("operation_ref mismatch: got=%q want=%q", got, want)
@@ -165,7 +303,7 @@ func TestToPaymentOperation_InfersGatewayFromStepCode(t *testing.T) {
StepRef: "step-2", StepRef: "step-2",
StepCode: "edge.1_2.ledger.debit", StepCode: "edge.1_2.ledger.debit",
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED, State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
}, nil) })
if got := op.OperationRef; got != "" { if got := op.OperationRef; got != "" {
t.Fatalf("expected empty operation_ref, got=%q", got) t.Fatalf("expected empty operation_ref, got=%q", got)
@@ -188,7 +326,7 @@ func TestToPaymentOperation_DoesNotFallbackToCardPayoutRef(t *testing.T) {
Ref: "payout-123", Ref: "payout-123",
}, },
}, },
}, nil) })
if got := op.OperationRef; got != "" { if got := op.OperationRef; got != "" {
t.Fatalf("expected empty operation_ref, got=%q", got) t.Fatalf("expected empty operation_ref, got=%q", got)
@@ -203,15 +341,28 @@ func TestToPaymentOperation_MapsAmount(t *testing.T) {
StepRef: "step-4", StepRef: "step-4",
StepCode: "hop.4.card_payout.send", StepCode: "hop.4.card_payout.send",
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED, State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
}, &quotationv2.PaymentQuote{ })
TransferPrincipalAmount: &moneyv1.Money{Amount: "110.00", Currency: "USDT"},
DestinationAmount: &moneyv1.Money{Amount: "100.00", Currency: "EUR"}, if got := op.Amount; got != nil {
t.Fatalf("expected nil amount without executed_money, got=%+v", got)
}
if got := op.ConvertedAmount; got != nil {
t.Fatalf("expected no converted_amount for non-fx operation, got=%+v", got)
}
}
func TestToPaymentOperation_PrefersExecutedMoney(t *testing.T) {
op := toPaymentOperation(&orchestrationv2.StepExecution{
StepRef: "step-4b",
StepCode: "hop.4.card_payout.send",
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
ExecutedMoney: &moneyv1.Money{Amount: "99.95", Currency: "EUR"},
}) })
if op.Amount == nil { if op.Amount == nil {
t.Fatal("expected amount to be mapped") t.Fatal("expected amount to be mapped")
} }
if got, want := op.Amount.Amount, "100.00"; got != want { if got, want := op.Amount.Amount, "99.95"; got != want {
t.Fatalf("amount.value mismatch: got=%q want=%q", got, want) t.Fatalf("amount.value mismatch: got=%q want=%q", got, want)
} }
if got, want := op.Amount.Currency, "EUR"; got != want { if got, want := op.Amount.Currency, "EUR"; got != want {
@@ -224,22 +375,14 @@ func TestToPaymentOperation_MapsAmount(t *testing.T) {
func TestToPaymentOperation_MapsFxTwoAmounts(t *testing.T) { func TestToPaymentOperation_MapsFxTwoAmounts(t *testing.T) {
op := toPaymentOperation(&orchestrationv2.StepExecution{ op := toPaymentOperation(&orchestrationv2.StepExecution{
StepRef: "step-5", StepRef: "step-5",
StepCode: "hop.2.settlement.fx_convert", StepCode: "hop.2.settlement.fx_convert",
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED, State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
}, &quotationv2.PaymentQuote{ ConvertedMoney: &moneyv1.Money{Amount: "100.00", Currency: "EUR"},
TransferPrincipalAmount: &moneyv1.Money{Amount: "110.00", Currency: "USDT"},
DestinationAmount: &moneyv1.Money{Amount: "100.00", Currency: "EUR"},
}) })
if op.Amount == nil { if got := op.Amount; got != nil {
t.Fatal("expected fx base amount to be mapped") t.Fatalf("expected nil base amount without executed_money, got=%+v", got)
}
if got, want := op.Amount.Amount, "110.00"; got != want {
t.Fatalf("base amount.value mismatch: got=%q want=%q", got, want)
}
if got, want := op.Amount.Currency, "USDT"; got != want {
t.Fatalf("base amount.currency mismatch: got=%q want=%q", got, want)
} }
if op.ConvertedAmount == nil { if op.ConvertedAmount == nil {
t.Fatal("expected fx converted amount to be mapped") t.Fatal("expected fx converted amount to be mapped")
@@ -251,3 +394,32 @@ func TestToPaymentOperation_MapsFxTwoAmounts(t *testing.T) {
t.Fatalf("converted amount.currency mismatch: got=%q want=%q", got, want) t.Fatalf("converted amount.currency mismatch: got=%q want=%q", got, want)
} }
} }
func TestToPaymentOperation_FxWithExecutedMoney_StillProvidesTwoAmounts(t *testing.T) {
op := toPaymentOperation(&orchestrationv2.StepExecution{
StepRef: "step-6",
StepCode: "hop.2.settlement.fx_convert",
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
ExecutedMoney: &moneyv1.Money{Amount: "109.50", Currency: "USDT"},
ConvertedMoney: &moneyv1.Money{Amount: "100.00", Currency: "EUR"},
})
if op.Amount == nil {
t.Fatal("expected fx base amount to be mapped")
}
if got, want := op.Amount.Amount, "109.50"; got != want {
t.Fatalf("base amount.value mismatch: got=%q want=%q", got, want)
}
if got, want := op.Amount.Currency, "USDT"; got != want {
t.Fatalf("base amount.currency mismatch: got=%q want=%q", got, want)
}
if op.ConvertedAmount == nil {
t.Fatal("expected fx quote amount to be mapped")
}
if got, want := op.ConvertedAmount.Amount, "100.00"; got != want {
t.Fatalf("converted amount.value mismatch: got=%q want=%q", got, want)
}
if got, want := op.ConvertedAmount.Currency, "EUR"; got != want {
t.Fatalf("converted amount.currency mismatch: got=%q want=%q", got, want)
}
}

View File

@@ -110,7 +110,7 @@ func Account2ClaimsForClient(a *model.Account, expiration int, clientID string)
paramNameName: t.Name, paramNameName: t.Name,
paramNameLocale: t.Locale, paramNameLocale: t.Locale,
paramNameClientID: t.ClientID, paramNameClientID: t.ClientID,
paramNameExpiration: int64(t.Expiration.Unix()), paramNameExpiration: t.Expiration.Unix(),
paramNamePending: t.Pending, paramNamePending: t.Pending,
} }
} }

View File

@@ -26,11 +26,11 @@ const (
var ( var (
ledgerDiscoveryServiceNames = []string{ ledgerDiscoveryServiceNames = []string{
"LEDGER", "LEDGER",
string(mservice.Ledger), mservice.Ledger,
} }
paymentOrchestratorDiscoveryServiceNames = []string{ paymentOrchestratorDiscoveryServiceNames = []string{
"PAYMENTS_ORCHESTRATOR", "PAYMENTS_ORCHESTRATOR",
string(mservice.PaymentOrchestrator), mservice.PaymentOrchestrator,
} }
paymentQuotationDiscoveryServiceNames = []string{ paymentQuotationDiscoveryServiceNames = []string{
"PAYMENTS_QUOTATION", "PAYMENTS_QUOTATION",
@@ -41,7 +41,7 @@ var (
paymentMethodsDiscoveryServiceNames = []string{ paymentMethodsDiscoveryServiceNames = []string{
"PAYMENTS_METHODS", "PAYMENTS_METHODS",
"PAYMENT_METHODS", "PAYMENT_METHODS",
string(mservice.PaymentMethods), mservice.PaymentMethods,
} }
) )
@@ -339,13 +339,13 @@ func selectGatewayEndpoint(gateways []discovery.GatewaySummary, preferredNetwork
func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) { func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
raw = strings.TrimSpace(raw) raw = strings.TrimSpace(raw)
if raw == "" { if raw == "" {
return discoveryEndpoint{}, fmt.Errorf("Invoke uri is empty") return discoveryEndpoint{}, fmt.Errorf("invoke uri is empty")
} }
// Without a scheme we expect a plain host:port target. // Without a scheme we expect a plain host:port target.
if !strings.Contains(raw, "://") { if !strings.Contains(raw, "://") {
if _, _, err := net.SplitHostPort(raw); err != nil { if _, _, err := net.SplitHostPort(raw); err != nil {
return discoveryEndpoint{}, fmt.Errorf("Invoke uri must include host:port: %w", err) return discoveryEndpoint{}, fmt.Errorf("invoke uri must include host:port: %w", err)
} }
return discoveryEndpoint{ return discoveryEndpoint{
address: raw, address: raw,
@@ -363,7 +363,7 @@ func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
case "grpc": case "grpc":
address := strings.TrimSpace(parsed.Host) address := strings.TrimSpace(parsed.Host)
if _, _, splitErr := net.SplitHostPort(address); splitErr != nil { if _, _, splitErr := net.SplitHostPort(address); splitErr != nil {
return discoveryEndpoint{}, fmt.Errorf("Grpc invoke uri must include host:port: %w", splitErr) return discoveryEndpoint{}, fmt.Errorf("grpc invoke uri must include host:port: %w", splitErr)
} }
return discoveryEndpoint{ return discoveryEndpoint{
address: address, address: address,
@@ -373,7 +373,7 @@ func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
case "grpcs": case "grpcs":
address := strings.TrimSpace(parsed.Host) address := strings.TrimSpace(parsed.Host)
if _, _, splitErr := net.SplitHostPort(address); splitErr != nil { if _, _, splitErr := net.SplitHostPort(address); splitErr != nil {
return discoveryEndpoint{}, fmt.Errorf("Grpcs invoke uri must include host:port: %w", splitErr) return discoveryEndpoint{}, fmt.Errorf("grpcs invoke uri must include host:port: %w", splitErr)
} }
return discoveryEndpoint{ return discoveryEndpoint{
address: address, address: address,
@@ -388,7 +388,7 @@ func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
raw: raw, raw: raw,
}, nil }, nil
default: default:
return discoveryEndpoint{}, fmt.Errorf("Unsupported invoke uri scheme: %s", parsed.Scheme) return discoveryEndpoint{}, fmt.Errorf("unsupported invoke uri scheme: %s", parsed.Scheme)
} }
} }

View File

@@ -43,6 +43,7 @@ func (d *DispatcherImpl) dispatchMessage(ctx context.Context, conn *websocket.Co
} }
func (d *DispatcherImpl) handle(w http.ResponseWriter, r *http.Request) { func (d *DispatcherImpl) handle(w http.ResponseWriter, r *http.Request) {
//nolint:contextcheck // websocket.Handler callback signature does not carry request context.
websocket.Handler(func(conn *websocket.Conn) { websocket.Handler(func(conn *websocket.Conn) {
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(d.timeout)*time.Second) ctx, cancel := context.WithTimeout(r.Context(), time.Duration(d.timeout)*time.Second)
defer cancel() defer cancel()

View File

@@ -71,6 +71,7 @@ func GetOptionalParam[T any](logger mlogger.Logger, r *http.Request, key string,
vals := r.URL.Query() vals := r.URL.Query()
s := vals.Get(key) s := vals.Get(key)
if s == "" { if s == "" {
//nolint:nilnil // Missing optional query parameter is represented as (nil, nil).
return nil, nil return nil, nil
} }

View File

@@ -1,17 +1,17 @@
package mutil package mutil
import ( import (
"context"
"net/http" "net/http"
"testing" "testing"
"github.com/tech/sendico/pkg/mlogger"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap" "go.uber.org/zap"
) )
func TestGetOptionalBoolParam(t *testing.T) { func TestGetOptionalBoolParam(t *testing.T) {
logger := mlogger.Logger(zap.NewNop()) logger := zap.NewNop()
tests := []struct { tests := []struct {
name string name string
@@ -47,7 +47,7 @@ func TestGetOptionalBoolParam(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com"+tt.query, nil) req, err := http.NewRequestWithContext(context.Background(), "GET", "http://example.com"+tt.query, nil)
require.NoError(t, err) require.NoError(t, err)
result, err := GetOptionalBoolParam(logger, req, "param") result, err := GetOptionalBoolParam(logger, req, "param")
@@ -69,7 +69,7 @@ func TestGetOptionalBoolParam(t *testing.T) {
} }
func TestGetOptionalInt64Param(t *testing.T) { func TestGetOptionalInt64Param(t *testing.T) {
logger := mlogger.Logger(zap.NewNop()) logger := zap.NewNop()
tests := []struct { tests := []struct {
name string name string
@@ -111,7 +111,7 @@ func TestGetOptionalInt64Param(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
req, err := http.NewRequest("GET", "http://example.com"+tt.query, nil) req, err := http.NewRequestWithContext(context.Background(), "GET", "http://example.com"+tt.query, nil)
require.NoError(t, err) require.NoError(t, err)
result, err := GetOptionalInt64Param(logger, req, "param") result, err := GetOptionalInt64Param(logger, req, "param")

View File

@@ -114,7 +114,7 @@ func (a *AccountAPI) deleteAll(r *http.Request, account *model.Account, token *s
a.logger.Warn("Failed to delete all data", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account)) a.logger.Warn("Failed to delete all data", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
return nil, err return nil, err
} }
return nil, nil return struct{}{}, nil
}); err != nil { }); err != nil {
a.logger.Warn("Failed to execute delete transaction", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account)) a.logger.Warn("Failed to execute delete transaction", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
return response.Auto(a.logger, a.Name(), err) return response.Auto(a.logger, a.Name(), err)

View File

@@ -192,5 +192,5 @@ func (a *AccountAPI) resetPasswordTransactionBody(ctx context.Context, user *mod
// Don't fail the transaction if token revocation fails, but log it // Don't fail the transaction if token revocation fails, but log it
} }
return nil, nil return struct{}{}, nil
} }

View File

@@ -133,12 +133,7 @@ func TestPasswordValidationLogic(t *testing.T) {
for _, password := range invalidPasswords { for _, password := range invalidPasswords {
t.Run(password, func(t *testing.T) { t.Run(password, func(t *testing.T) {
// Test that invalid passwords fail at least one requirement // Test that invalid passwords fail at least one requirement
isValid := true isValid := len(password) >= 8
// Check length
if len(password) < 8 {
isValid = false
}
// Check for digit // Check for digit
hasDigit := false hasDigit := false

View File

@@ -276,7 +276,7 @@ func (a *AccountAPI) grantAllPermissions(ctx context.Context, organizationRef bs
for resource, granted := range required { for resource, granted := range required {
if !granted { if !granted {
a.logger.Warn("Required policy description not found for signup permissions", zap.String("resource", string(resource))) a.logger.Warn("Required policy description not found for signup permissions", zap.String("resource", resource))
} }
} }
@@ -338,9 +338,6 @@ func (a *AccountAPI) openOrgLedgerAccount(ctx context.Context, org *model.Organi
return merrors.Internal("chain gateway default asset is not configured") return merrors.Internal("chain gateway default asset is not configured")
} }
// TODO: remove hardcode
currency := "RUB"
var describable *describablev1.Describable var describable *describablev1.Describable
name := strings.TrimSpace(sr.LedgerWallet.Name) name := strings.TrimSpace(sr.LedgerWallet.Name)
var description *string var description *string
@@ -357,26 +354,47 @@ func (a *AccountAPI) openOrgLedgerAccount(ctx context.Context, org *model.Organi
} }
} }
resp, err := a.ledgerClient.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{ currencies := []string{"RUB", "USDT"}
OrganizationRef: org.ID.Hex(), if chainTokenCurrency := strings.ToUpper(strings.TrimSpace(a.chainAsset.GetTokenSymbol())); chainTokenCurrency != "" {
AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, currencies = append(currencies, chainTokenCurrency)
Currency: currency, }
Status: ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE,
Role: ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, seen := make(map[string]struct{}, len(currencies))
Metadata: map[string]string{ for _, currency := range currencies {
"source": "signup", currency = strings.ToUpper(strings.TrimSpace(currency))
"login": sr.Account.Login, if currency == "" {
}, continue
Describable: describable, }
}) if _, exists := seen[currency]; exists {
if err != nil { continue
a.logger.Warn("Failed to create ledger account for organization", zap.Error(err), mzap.StorableRef(org)) }
return err seen[currency] = struct{}{}
}
if resp == nil || resp.GetAccount() == nil || strings.TrimSpace(resp.GetAccount().GetLedgerAccountRef()) == "" { resp, err := a.ledgerClient.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
return merrors.Internal("ledger returned empty account reference") OrganizationRef: org.ID.Hex(),
AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_ASSET,
Currency: currency,
Status: ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE,
Role: ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING,
Metadata: map[string]string{
"source": "signup",
"login": sr.Account.Login,
},
Describable: describable,
})
if err != nil {
a.logger.Warn("Failed to create ledger account for organization", zap.Error(err), mzap.StorableRef(org), zap.String("currency", currency))
return err
}
if resp == nil || resp.GetAccount() == nil || strings.TrimSpace(resp.GetAccount().GetLedgerAccountRef()) == "" {
return merrors.Internal("ledger returned empty account reference")
}
a.logger.Info("Ledger account created for organization",
mzap.StorableRef(org),
zap.String("currency", currency),
zap.String("ledger_account_ref", resp.GetAccount().GetLedgerAccountRef()))
} }
a.logger.Info("Ledger account created for organization", mzap.StorableRef(org), zap.String("ledger_account_ref", resp.GetAccount().GetLedgerAccountRef()))
return nil return nil
} }

View File

@@ -126,7 +126,7 @@ func TestSignupHTTPSerialization(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
// Create HTTP request // Create HTTP request
req := httptest.NewRequest(http.MethodPost, "/signup", bytes.NewBuffer(reqBody)) req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/signup", bytes.NewBuffer(reqBody))
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
// Parse the request body // Parse the request body
@@ -162,7 +162,7 @@ func TestSignupHTTPSerialization(t *testing.T) {
t.Run("InvalidJSONRequest", func(t *testing.T) { t.Run("InvalidJSONRequest", func(t *testing.T) {
invalidJSON := `{"account": {"login": "test@example.com", "password": "invalid json structure` invalidJSON := `{"account": {"login": "test@example.com", "password": "invalid json structure`
req := httptest.NewRequest(http.MethodPost, "/signup", bytes.NewBufferString(invalidJSON)) req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/signup", bytes.NewBufferString(invalidJSON))
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
var parsedRequest srequest.Signup var parsedRequest srequest.Signup

View File

@@ -16,13 +16,13 @@ import (
) )
type stubLedgerAccountClient struct { type stubLedgerAccountClient struct {
createReq *ledgerv1.CreateAccountRequest createReqs []*ledgerv1.CreateAccountRequest
createResp *ledgerv1.CreateAccountResponse createResp *ledgerv1.CreateAccountResponse
createErr error createErr error
} }
func (s *stubLedgerAccountClient) CreateAccount(_ context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) { func (s *stubLedgerAccountClient) CreateAccount(_ context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
s.createReq = req s.createReqs = append(s.createReqs, req)
return s.createResp, s.createErr return s.createResp, s.createErr
} }
@@ -31,7 +31,7 @@ func (s *stubLedgerAccountClient) Close() error {
} }
func TestOpenOrgLedgerAccount(t *testing.T) { func TestOpenOrgLedgerAccount(t *testing.T) {
t.Run("creates operating ledger account", func(t *testing.T) { t.Run("creates operating ledger accounts for RUB and USDT", func(t *testing.T) {
desc := " Main org ledger account " desc := " Main org ledger account "
sr := &srequest.Signup{ sr := &srequest.Signup{
Account: model.AccountData{ Account: model.AccountData{
@@ -65,22 +65,26 @@ func TestOpenOrgLedgerAccount(t *testing.T) {
err := api.openOrgLedgerAccount(context.Background(), org, sr) err := api.openOrgLedgerAccount(context.Background(), org, sr)
assert.NoError(t, err) assert.NoError(t, err)
if assert.NotNil(t, ledgerStub.createReq) { if assert.Len(t, ledgerStub.createReqs, 2) {
assert.Equal(t, org.ID.Hex(), ledgerStub.createReq.GetOrganizationRef()) currencies := make([]string, 0, len(ledgerStub.createReqs))
assert.Equal(t, "RUB", ledgerStub.createReq.GetCurrency()) for _, req := range ledgerStub.createReqs {
assert.Equal(t, ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, ledgerStub.createReq.GetAccountType()) currencies = append(currencies, req.GetCurrency())
assert.Equal(t, ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, ledgerStub.createReq.GetStatus()) assert.Equal(t, org.ID.Hex(), req.GetOrganizationRef())
assert.Equal(t, ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, ledgerStub.createReq.GetRole()) assert.Equal(t, ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, req.GetAccountType())
assert.Equal(t, map[string]string{ assert.Equal(t, ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, req.GetStatus())
"source": "signup", assert.Equal(t, ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, req.GetRole())
"login": "owner@example.com", assert.Equal(t, map[string]string{
}, ledgerStub.createReq.GetMetadata()) "source": "signup",
if assert.NotNil(t, ledgerStub.createReq.GetDescribable()) { "login": "owner@example.com",
assert.Equal(t, "Primary Ledger", ledgerStub.createReq.GetDescribable().GetName()) }, req.GetMetadata())
if assert.NotNil(t, ledgerStub.createReq.GetDescribable().Description) { if assert.NotNil(t, req.GetDescribable()) {
assert.Equal(t, "Main org ledger account", ledgerStub.createReq.GetDescribable().GetDescription()) assert.Equal(t, "Primary Ledger", req.GetDescribable().GetName())
if assert.NotNil(t, req.GetDescribable().Description) {
assert.Equal(t, "Main org ledger account", req.GetDescribable().GetDescription())
}
} }
} }
assert.ElementsMatch(t, []string{"RUB", "USDT"}, currencies)
} }
}) })

View File

@@ -37,7 +37,7 @@ func (a *CallbacksAPI) create(r *http.Request, account *model.Account, accessTok
if err := a.applySigningSecretMutation(ctx, *account.GetID(), *callback.GetID(), mutation); err != nil { if err := a.applySigningSecretMutation(ctx, *account.GetID(), *callback.GetID(), mutation); err != nil {
return nil, err return nil, err
} }
return nil, nil return struct{}{}, nil
}); err != nil { }); err != nil {
a.Logger.Warn("Failed to create callback transaction", zap.Error(err)) a.Logger.Warn("Failed to create callback transaction", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err) return response.Auto(a.Logger, a.Name(), err)

View File

@@ -49,7 +49,7 @@ func (a *CallbacksAPI) update(r *http.Request, account *model.Account, accessTok
if err := a.applySigningSecretMutation(ctx, *account.GetID(), callbackRef, mutation); err != nil { if err := a.applySigningSecretMutation(ctx, *account.GetID(), callbackRef, mutation); err != nil {
return nil, err return nil, err
} }
return nil, nil return struct{}{}, nil
}); err != nil { }); err != nil {
a.Logger.Warn("Failed to update callback transaction", zap.Error(err)) a.Logger.Warn("Failed to update callback transaction", zap.Error(err))
return response.Auto(a.Logger, a.Name(), err) return response.Auto(a.Logger, a.Name(), err)

View File

@@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/tech/sendico/pkg/api/http/response" "github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/domainprovider" "github.com/tech/sendico/pkg/domainprovider"
@@ -34,7 +35,10 @@ func (storage *LocalStorage) Delete(ctx context.Context, objID string) error {
default: default:
} }
filePath := filepath.Join(storage.storageDir, objID) filePath, err := storage.resolvePath(objID)
if err != nil {
return err
}
if err := os.Remove(filePath); err != nil { if err := os.Remove(filePath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
storage.logger.Debug("File not found", zap.String("obj_ref", objID)) storage.logger.Debug("File not found", zap.String("obj_ref", objID))
@@ -54,7 +58,11 @@ func (storage *LocalStorage) Save(ctx context.Context, file io.Reader, objID str
default: default:
} }
filePath := filepath.Join(storage.storageDir, objID) filePath, err := storage.resolvePath(objID)
if err != nil {
return "", err
}
//nolint:gosec // File path is resolved and constrained to storage root.
dst, err := os.Create(filePath) dst, err := os.Create(filePath)
if err != nil { if err != nil {
storage.logger.Warn("Error occurred while creating file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objID)) storage.logger.Warn("Error occurred while creating file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objID))
@@ -78,7 +86,9 @@ func (storage *LocalStorage) Save(ctx context.Context, file io.Reader, objID str
} }
case <-ctx.Done(): case <-ctx.Done():
// Context was cancelled, clean up the partial file // Context was cancelled, clean up the partial file
os.Remove(filePath) if removeErr := os.Remove(filePath); removeErr != nil && !os.IsNotExist(removeErr) {
storage.logger.Warn("Failed to remove partially written file", zap.Error(removeErr), zap.String("obj_ref", objID))
}
return "", ctx.Err() return "", ctx.Err()
} }
@@ -93,7 +103,10 @@ func (storage *LocalStorage) Get(ctx context.Context, objRef string) http.Handle
default: default:
} }
filePath := filepath.Join(storage.storageDir, objRef) filePath, err := storage.resolvePath(objRef)
if err != nil {
return response.Internal(storage.logger, storage.service, err)
}
if _, err := os.Stat(filePath); err != nil { if _, err := os.Stat(filePath); err != nil {
storage.logger.Warn("Failed to access file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objRef)) storage.logger.Warn("Failed to access file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objRef))
return response.Internal(storage.logger, storage.service, err) return response.Internal(storage.logger, storage.service, err)
@@ -117,7 +130,7 @@ func (storage *LocalStorage) Get(ctx context.Context, objRef string) http.Handle
func ensureDir(dirName string) error { func ensureDir(dirName string) error {
info, err := os.Stat(dirName) info, err := os.Stat(dirName)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return os.MkdirAll(dirName, 0o755) return os.MkdirAll(dirName, 0o750)
} }
if err != nil { if err != nil {
return err return err
@@ -128,6 +141,24 @@ func ensureDir(dirName string) error {
return nil return nil
} }
func (storage *LocalStorage) resolvePath(objID string) (string, error) {
objID = strings.TrimSpace(objID)
if objID == "" {
return "", merrors.InvalidArgument("obj_ref is required", "obj_ref")
}
filePath := filepath.Join(storage.storageDir, objID)
relPath, err := filepath.Rel(storage.storageDir, filePath)
if err != nil {
return "", merrors.InternalWrap(err, "failed to resolve local file path")
}
if relPath == "." || strings.HasPrefix(relPath, "..") {
return "", merrors.InvalidArgument("obj_ref is invalid", "obj_ref")
}
return filePath, nil
}
func CreateLocalFileStorage(logger mlogger.Logger, service mservice.Type, directory, subDir string, dp domainprovider.DomainProvider, cfg config.LocalFSSConfig) (*LocalStorage, error) { func CreateLocalFileStorage(logger mlogger.Logger, service mservice.Type, directory, subDir string, dp domainprovider.DomainProvider, cfg config.LocalFSSConfig) (*LocalStorage, error) {
dir := filepath.Join(cfg.RootPath, directory) dir := filepath.Join(cfg.RootPath, directory)
if err := ensureDir(dir); err != nil { if err := ensureDir(dir); err != nil {

View File

@@ -55,7 +55,7 @@ func setupTestStorage(t *testing.T) (*LocalStorage, string, func()) {
// Return cleanup function // Return cleanup function
cleanup := func() { cleanup := func() {
os.RemoveAll(tempDir) require.NoError(t, os.RemoveAll(tempDir))
} }
return storage, tempDir, cleanup return storage, tempDir, cleanup
@@ -81,7 +81,7 @@ func setupBenchmarkStorage(b *testing.B) (*LocalStorage, string, func()) {
// Return cleanup function // Return cleanup function
cleanup := func() { cleanup := func() {
os.RemoveAll(tempDir) require.NoError(b, os.RemoveAll(tempDir))
} }
return storage, tempDir, cleanup return storage, tempDir, cleanup
@@ -138,6 +138,7 @@ func TestLocalStorage_Save(t *testing.T) {
// Verify file was actually saved // Verify file was actually saved
filePath := filepath.Join(tempDir, tt.objID) filePath := filepath.Join(tempDir, tt.objID)
//nolint:gosec // Test-controlled path inside temporary directory.
content, err := os.ReadFile(filePath) content, err := os.ReadFile(filePath)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, tt.content, string(content)) assert.Equal(t, tt.content, string(content))
@@ -186,7 +187,7 @@ func TestLocalStorage_Delete(t *testing.T) {
// Create a test file // Create a test file
testFile := filepath.Join(tempDir, "test.txt") testFile := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0o644) err := os.WriteFile(testFile, []byte("test content"), 0o600)
require.NoError(t, err) require.NoError(t, err)
tests := []struct { tests := []struct {
@@ -232,7 +233,7 @@ func TestLocalStorage_Delete_ContextCancellation(t *testing.T) {
// Create a test file // Create a test file
testFile := filepath.Join(tempDir, "test.txt") testFile := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0o644) err := os.WriteFile(testFile, []byte("test content"), 0o600)
require.NoError(t, err) require.NoError(t, err)
// Create a context that's already cancelled // Create a context that's already cancelled
@@ -256,7 +257,7 @@ func TestLocalStorage_Get(t *testing.T) {
// Create a test file // Create a test file
testContent := "test file content" testContent := "test file content"
testFile := filepath.Join(tempDir, "test.txt") testFile := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFile, []byte(testContent), 0o644) err := os.WriteFile(testFile, []byte(testContent), 0o600)
require.NoError(t, err) require.NoError(t, err)
tests := []struct { tests := []struct {
@@ -285,7 +286,7 @@ func TestLocalStorage_Get(t *testing.T) {
handler := storage.Get(ctx, tt.objID) handler := storage.Get(ctx, tt.objID)
// Create test request // Create test request
req := httptest.NewRequest("GET", "/files/"+tt.objID, nil) req := httptest.NewRequestWithContext(context.Background(), "GET", "/files/"+tt.objID, nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
@@ -304,7 +305,7 @@ func TestLocalStorage_Get_ContextCancellation(t *testing.T) {
// Create a test file // Create a test file
testFile := filepath.Join(tempDir, "test.txt") testFile := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0o644) err := os.WriteFile(testFile, []byte("test content"), 0o600)
require.NoError(t, err) require.NoError(t, err)
// Create a context that's already cancelled // Create a context that's already cancelled
@@ -314,7 +315,7 @@ func TestLocalStorage_Get_ContextCancellation(t *testing.T) {
handler := storage.Get(ctx, "test.txt") handler := storage.Get(ctx, "test.txt")
// Create test request // Create test request
req := httptest.NewRequest("GET", "/files/test.txt", nil) req := httptest.NewRequestWithContext(context.Background(), "GET", "/files/test.txt", nil)
w := httptest.NewRecorder() w := httptest.NewRecorder()
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
@@ -328,14 +329,14 @@ func TestLocalStorage_Get_RequestContextCancellation(t *testing.T) {
// Create a test file // Create a test file
testFile := filepath.Join(tempDir, "test.txt") testFile := filepath.Join(tempDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0o644) err := os.WriteFile(testFile, []byte("test content"), 0o600)
require.NoError(t, err) require.NoError(t, err)
ctx := context.Background() ctx := context.Background()
handler := storage.Get(ctx, "test.txt") handler := storage.Get(ctx, "test.txt")
// Create test request with cancelled context // Create test request with cancelled context
req := httptest.NewRequest("GET", "/files/test.txt", nil) req := httptest.NewRequestWithContext(context.Background(), "GET", "/files/test.txt", nil)
reqCtx, cancel := context.WithCancel(req.Context()) reqCtx, cancel := context.WithCancel(req.Context())
req = req.WithContext(reqCtx) req = req.WithContext(reqCtx)
cancel() // Cancel the request context cancel() // Cancel the request context
@@ -352,7 +353,9 @@ func TestCreateLocalFileStorage(t *testing.T) {
// Create temporary directory for testing // Create temporary directory for testing
tempDir, err := os.MkdirTemp("", "storage_test") tempDir, err := os.MkdirTemp("", "storage_test")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tempDir) defer func() {
require.NoError(t, os.RemoveAll(tempDir))
}()
logger := zap.NewNop() logger := zap.NewNop()
cfg := config.LocalFSSConfig{ cfg := config.LocalFSSConfig{
@@ -372,10 +375,12 @@ func TestCreateLocalFileStorage_InvalidPath(t *testing.T) {
// Build a deterministic failure case: the target path already exists as a file. // Build a deterministic failure case: the target path already exists as a file.
tempDir, err := os.MkdirTemp("", "storage_invalid_path_test") tempDir, err := os.MkdirTemp("", "storage_invalid_path_test")
require.NoError(t, err) require.NoError(t, err)
defer os.RemoveAll(tempDir) defer func() {
require.NoError(t, os.RemoveAll(tempDir))
}()
fileAtTargetPath := filepath.Join(tempDir, "test") fileAtTargetPath := filepath.Join(tempDir, "test")
err = os.WriteFile(fileAtTargetPath, []byte("not a directory"), 0o644) err = os.WriteFile(fileAtTargetPath, []byte("not a directory"), 0o600)
require.NoError(t, err) require.NoError(t, err)
logger := zap.NewNop() logger := zap.NewNop()
@@ -426,7 +431,7 @@ func TestLocalStorage_ConcurrentOperations(t *testing.T) {
// Create files to delete // Create files to delete
for i := 0; i < 5; i++ { for i := 0; i < 5; i++ {
filePath := filepath.Join(tempDir, fmt.Sprintf("delete_%d.txt", i)) filePath := filepath.Join(tempDir, fmt.Sprintf("delete_%d.txt", i))
err := os.WriteFile(filePath, []byte("content"), 0o644) err := os.WriteFile(filePath, []byte("content"), 0o600)
require.NoError(t, err) require.NoError(t, err)
} }
@@ -536,7 +541,7 @@ func BenchmarkLocalStorage_Delete(b *testing.B) {
// Pre-create files for deletion // Pre-create files for deletion
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
filePath := filepath.Join(tempDir, fmt.Sprintf("bench_delete_%d.txt", i)) filePath := filepath.Join(tempDir, fmt.Sprintf("bench_delete_%d.txt", i))
err := os.WriteFile(filePath, []byte("content"), 0o644) err := os.WriteFile(filePath, []byte("content"), 0o600)
if err != nil { if err != nil {
b.Fatal(err) b.Fatal(err)
} }

View File

@@ -97,7 +97,9 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
} }
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) { func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
defer r.Body.Close() defer func() {
_ = r.Body.Close()
}()
payload := srequest.CreateLedgerAccount{} payload := srequest.CreateLedgerAccount{}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {

View File

@@ -46,7 +46,7 @@ func (a *ProtectedAPI[T]) archive(r *http.Request, account *model.Account, acces
ctx := r.Context() ctx := r.Context()
_, err = a.a.DBFactory().TransactionFactory().CreateTransaction().Execute(ctx, func(ctx context.Context) (any, error) { _, err = a.a.DBFactory().TransactionFactory().CreateTransaction().Execute(ctx, func(ctx context.Context) (any, error) {
return nil, a.DB.SetArchived(r.Context(), *account.GetID(), organizationRef, objectRef, *archived, *cascade) return nil, a.DB.SetArchived(ctx, *account.GetID(), organizationRef, objectRef, *archived, *cascade)
}) })
if err != nil { if err != nil {
a.Logger.Warn("Failed to change archive property", zap.Error(err), mzap.StorableRef(account), mutil.PLog(a.Cph, r), a.Logger.Warn("Failed to change archive property", zap.Error(err), mzap.StorableRef(account), mutil.PLog(a.Cph, r),

View File

@@ -69,7 +69,7 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
op, err := a.fetchGatewayOperation(r.Context(), gateway.InvokeURI, operationRef) op, err := a.fetchGatewayOperation(r.Context(), gateway.InvokeURI, operationRef)
if err != nil { if err != nil {
a.logger.Warn("Failed to fetch gateway operation for document generation", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", string(gatewayService)), zap.String("operation_ref", operationRef)) a.logger.Warn("Failed to fetch gateway operation for document generation", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", gatewayService), zap.String("operation_ref", operationRef))
return documentErrorResponse(a.logger, a.Name(), err) return documentErrorResponse(a.logger, a.Name(), err)
} }
@@ -77,7 +77,7 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req) docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req)
if err != nil { if err != nil {
a.logger.Warn("Failed to fetch operation document", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", string(gatewayService)), zap.String("operation_ref", operationRef)) a.logger.Warn("Failed to fetch operation document", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", gatewayService), zap.String("operation_ref", operationRef))
return documentErrorResponse(a.logger, a.Name(), err) return documentErrorResponse(a.logger, a.Name(), err)
} }
@@ -154,6 +154,7 @@ func operationDocumentResponse(logger mlogger.Logger, source mservice.Type, docR
w.Header().Set("Content-Type", mimeType) w.Header().Set("Content-Type", mimeType)
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename)) w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
//nolint:gosec // Binary payload is served as attachment with explicit content type.
if _, err := w.Write(docResp.GetContent()); err != nil { if _, err := w.Write(docResp.GetContent()); err != nil {
logger.Warn("Failed to write document response", zap.Error(err)) logger.Warn("Failed to write document response", zap.Error(err))
} }
@@ -167,15 +168,15 @@ func normalizeGatewayService(raw string) mservice.Type {
} }
switch value { switch value {
case string(mservice.ChainGateway): case mservice.ChainGateway:
return mservice.ChainGateway return mservice.ChainGateway
case string(mservice.TronGateway): case mservice.TronGateway:
return mservice.TronGateway return mservice.TronGateway
case string(mservice.MntxGateway): case mservice.MntxGateway:
return mservice.MntxGateway return mservice.MntxGateway
case string(mservice.PaymentGateway): case mservice.PaymentGateway:
return mservice.PaymentGateway return mservice.PaymentGateway
case string(mservice.TgSettle): case mservice.TgSettle:
return mservice.TgSettle return mservice.TgSettle
default: default:
return "" return ""
@@ -219,7 +220,11 @@ func (a *PaymentAPI) fetchOperationDocument(ctx context.Context, invokeURI strin
if err != nil { if err != nil {
return nil, merrors.InternalWrap(err, "dial billing documents") return nil, merrors.InternalWrap(err, "dial billing documents")
} }
defer conn.Close() defer func() {
if closeErr := conn.Close(); closeErr != nil {
a.logger.Warn("Failed to close billing documents connection", zap.Error(closeErr))
}
}()
client := documentsv1.NewDocumentServiceClient(conn) client := documentsv1.NewDocumentServiceClient(conn)
@@ -234,7 +239,11 @@ func (a *PaymentAPI) fetchGatewayOperation(ctx context.Context, invokeURI, opera
if err != nil { if err != nil {
return nil, merrors.InternalWrap(err, "dial gateway connector") return nil, merrors.InternalWrap(err, "dial gateway connector")
} }
defer conn.Close() defer func() {
if closeErr := conn.Close(); closeErr != nil {
a.logger.Warn("Failed to close gateway connector connection", zap.Error(closeErr))
}
}()
client := connectorv1.NewConnectorServiceClient(conn) client := connectorv1.NewConnectorServiceClient(conn)
@@ -307,7 +316,7 @@ func findGatewayForService(gateways []discovery.GatewaySummary, gatewayService m
func operationDocumentRequest(organizationRef string, gatewayService mservice.Type, requestedOperationRef string, op *connectorv1.Operation) *documentsv1.GetOperationDocumentRequest { func operationDocumentRequest(organizationRef string, gatewayService mservice.Type, requestedOperationRef string, op *connectorv1.Operation) *documentsv1.GetOperationDocumentRequest {
req := &documentsv1.GetOperationDocumentRequest{ req := &documentsv1.GetOperationDocumentRequest{
OrganizationRef: strings.TrimSpace(organizationRef), OrganizationRef: strings.TrimSpace(organizationRef),
GatewayService: string(gatewayService), GatewayService: gatewayService,
OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)), OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)),
OperationCode: strings.TrimSpace(op.GetType().String()), OperationCode: strings.TrimSpace(op.GetType().String()),
OperationLabel: operationLabel(op.GetType()), OperationLabel: operationLabel(op.GetType()),

View File

@@ -103,6 +103,7 @@ func listPaymentsPage(r *http.Request) (*paginationv1.CursorPageRequest, error)
} }
if cursor == "" && !hasLimit { if cursor == "" && !hasLimit {
//nolint:nilnil // Absent pagination params mean no page request should be sent downstream.
return nil, nil return nil, nil
} }
@@ -189,6 +190,7 @@ func firstNonEmpty(values ...string) string {
func parseRFC3339Timestamp(raw string, field string) (*timestamppb.Timestamp, error) { func parseRFC3339Timestamp(raw string, field string) (*timestamppb.Timestamp, error) {
trimmed := strings.TrimSpace(raw) trimmed := strings.TrimSpace(raw)
if trimmed == "" { if trimmed == "" {
//nolint:nilnil // Empty timestamp filter is represented as (nil, nil).
return nil, nil return nil, nil
} }
parsed, err := time.Parse(time.RFC3339, trimmed) parsed, err := time.Parse(time.RFC3339, trimmed)

View File

@@ -61,9 +61,7 @@ func mapQuoteIntent(intent *srequest.PaymentIntent) (*quotationv2.QuoteIntent, e
FeeTreatment: resolvedFeeTreatment, FeeTreatment: resolvedFeeTreatment,
SettlementCurrency: settlementCurrency, SettlementCurrency: settlementCurrency,
Fx: mapFXIntent(intent), Fx: mapFXIntent(intent),
} Comment: strings.TrimSpace(intent.Comment),
if comment := strings.TrimSpace(intent.Attributes["comment"]); comment != "" {
quoteIntent.Comment = comment
} }
return quoteIntent, nil return quoteIntent, nil
} }

View File

@@ -102,7 +102,9 @@ func (a *PaymentAPI) initiatePayment(r *http.Request, account *model.Account, to
} }
func decodeInitiatePayload(r *http.Request) (*srequest.InitiatePayment, error) { func decodeInitiatePayload(r *http.Request) (*srequest.InitiatePayment, error) {
defer r.Body.Close() defer func() {
_ = r.Body.Close()
}()
payload := &srequest.InitiatePayment{} payload := &srequest.InitiatePayment{}
if err := json.NewDecoder(r.Body).Decode(payload); err != nil { if err := json.NewDecoder(r.Body).Decode(payload); err != nil {

View File

@@ -68,7 +68,7 @@ func TestInitiateByQuote_RejectsMetadataIntentRef(t *testing.T) {
func invokeInitiateByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder { func invokeInitiateByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder {
t.Helper() t.Helper()
req := httptest.NewRequest(http.MethodPost, "/by-quote", bytes.NewBufferString(body)) req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/by-quote", bytes.NewBufferString(body))
routeCtx := chi.NewRouteContext() routeCtx := chi.NewRouteContext()
routeCtx.URLParams.Add("organizations_ref", orgRef.Hex()) routeCtx.URLParams.Add("organizations_ref", orgRef.Hex())
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx)) req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))

View File

@@ -63,7 +63,9 @@ func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Acc
} }
func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments, error) { func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments, error) {
defer r.Body.Close() defer func() {
_ = r.Body.Close()
}()
payload := &srequest.InitiatePayments{} payload := &srequest.InitiatePayments{}
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(r.Body)

View File

@@ -10,7 +10,6 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/tech/sendico/pkg/auth" "github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model" "github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/pkg/mservice" "github.com/tech/sendico/pkg/mservice"
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2" orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
@@ -118,7 +117,7 @@ func TestInitiatePaymentsByQuote_RejectsDeprecatedIntentRefsField(t *testing.T)
func newBatchAPI(exec executionClient) *PaymentAPI { func newBatchAPI(exec executionClient) *PaymentAPI {
return &PaymentAPI{ return &PaymentAPI{
logger: mlogger.Logger(zap.NewNop()), logger: zap.NewNop(),
execution: exec, execution: exec,
enf: fakeEnforcerForBatch{allowed: true}, enf: fakeEnforcerForBatch{allowed: true},
oph: mutil.CreatePH(mservice.Organizations), oph: mutil.CreatePH(mservice.Organizations),
@@ -129,7 +128,7 @@ func newBatchAPI(exec executionClient) *PaymentAPI {
func invokeInitiatePaymentsByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder { func invokeInitiatePaymentsByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder {
t.Helper() t.Helper()
req := httptest.NewRequest(http.MethodPost, "/by-multiquote", bytes.NewBufferString(body)) req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/by-multiquote", bytes.NewBufferString(body))
routeCtx := chi.NewRouteContext() routeCtx := chi.NewRouteContext()
routeCtx.URLParams.Add("organizations_ref", orgRef.Hex()) routeCtx.URLParams.Add("organizations_ref", orgRef.Hex())
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx)) req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))
@@ -177,6 +176,7 @@ func (f fakeEnforcerForBatch) Enforce(context.Context, bson.ObjectID, bson.Objec
} }
func (fakeEnforcerForBatch) EnforceBatch(context.Context, []model.PermissionBoundStorable, bson.ObjectID, model.Action) (map[bson.ObjectID]bool, error) { func (fakeEnforcerForBatch) EnforceBatch(context.Context, []model.PermissionBoundStorable, bson.ObjectID, model.Action) (map[bson.ObjectID]bool, error) {
//nolint:nilnil // Test stub does not provide batch permissions map.
return nil, nil return nil, nil
} }

View File

@@ -126,7 +126,9 @@ func (a *PaymentAPI) quotePayments(r *http.Request, account *model.Account, toke
} }
func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) { func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
defer r.Body.Close() defer func() {
_ = r.Body.Close()
}()
payload := &srequest.QuotePayment{} payload := &srequest.QuotePayment{}
if err := json.NewDecoder(r.Body).Decode(payload); err != nil { if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
@@ -140,7 +142,9 @@ func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
} }
func decodeQuotePaymentsPayload(r *http.Request) (*srequest.QuotePayments, error) { func decodeQuotePaymentsPayload(r *http.Request) (*srequest.QuotePayments, error) {
defer r.Body.Close() defer func() {
_ = r.Body.Close()
}()
payload := &srequest.QuotePayments{} payload := &srequest.QuotePayments{}
if err := json.NewDecoder(r.Body).Decode(payload); err != nil { if err := json.NewDecoder(r.Body).Decode(payload); err != nil {

View File

@@ -256,6 +256,7 @@ func (c *grpcQuotationClient) callContext(ctx context.Context) (context.Context,
if timeout <= 0 { if timeout <= 0 {
timeout = 3 * time.Second timeout = 3 * time.Second
} }
//nolint:gosec // Caller receives cancel func and defers it in every call path.
return context.WithTimeout(ctx, timeout) return context.WithTimeout(ctx, timeout)
} }
@@ -271,7 +272,7 @@ func (a *PaymentAPI) initDiscoveryClient(cfg *eapi.Config) error {
if err != nil { if err != nil {
return err return err
} }
client, err := discovery.NewClient(a.logger, broker, nil, string(a.Name())) client, err := discovery.NewClient(a.logger, broker, nil, a.Name())
if err != nil { if err != nil {
return err return err
} }

View File

@@ -90,5 +90,5 @@ func (a *PermissionsAPI) changePoliciesImp(
} }
} }
return nil, nil return struct{}{}, nil
} }

View File

@@ -223,6 +223,7 @@ func (a *WalletAPI) queryBalanceFromGateways(ctx context.Context, gateways []dis
a.logger.Debug("Wallet balance fan-out completed without result", a.logger.Debug("Wallet balance fan-out completed without result",
zap.String("organization_ref", organizationRef), zap.String("organization_ref", organizationRef),
zap.String("wallet_ref", walletRef)) zap.String("wallet_ref", walletRef))
//nolint:nilnil // No gateway returned a balance and no hard error occurred.
return nil, nil return nil, nil
} }
@@ -238,7 +239,11 @@ func (a *WalletAPI) queryGatewayBalance(ctx context.Context, gateway discovery.G
if err != nil { if err != nil {
return nil, merrors.InternalWrap(err, "dial gateway") return nil, merrors.InternalWrap(err, "dial gateway")
} }
defer conn.Close() defer func() {
if closeErr := conn.Close(); closeErr != nil {
a.logger.Warn("Failed to close gateway connection", zap.Error(closeErr), zap.String("gateway", gateway.ID))
}
}()
client := connectorv1.NewConnectorServiceClient(conn) client := connectorv1.NewConnectorServiceClient(conn)

View File

@@ -173,7 +173,11 @@ func (a *WalletAPI) createWalletOnGateway(ctx context.Context, gateway discovery
if err != nil { if err != nil {
return "", merrors.InternalWrap(err, "dial gateway") return "", merrors.InternalWrap(err, "dial gateway")
} }
defer conn.Close() defer func() {
if closeErr := conn.Close(); closeErr != nil {
a.logger.Warn("Failed to close gateway connection", zap.Error(closeErr), zap.String("gateway", gateway.ID))
}
}()
client := connectorv1.NewConnectorServiceClient(conn) client := connectorv1.NewConnectorServiceClient(conn)

View File

@@ -226,7 +226,11 @@ func (a *WalletAPI) queryGateway(ctx context.Context, gateway discovery.GatewayS
if err != nil { if err != nil {
return nil, merrors.InternalWrap(err, "dial gateway") return nil, merrors.InternalWrap(err, "dial gateway")
} }
defer conn.Close() defer func() {
if closeErr := conn.Close(); closeErr != nil {
a.logger.Warn("Failed to close gateway connection", zap.Error(closeErr), zap.String("gateway", gateway.ID))
}
}()
client := connectorv1.NewConnectorServiceClient(conn) client := connectorv1.NewConnectorServiceClient(conn)

View File

@@ -64,18 +64,21 @@ func (a *WalletAPI) rememberWalletRoute(ctx context.Context, organizationRef str
func (a *WalletAPI) walletRoute(ctx context.Context, organizationRef string, walletRef string) (*model.ChainWalletRoute, error) { func (a *WalletAPI) walletRoute(ctx context.Context, organizationRef string, walletRef string) (*model.ChainWalletRoute, error) {
if a.routes == nil { if a.routes == nil {
//nolint:nilnil // Routing cache is optional and may be disabled.
return nil, nil return nil, nil
} }
walletRef = strings.TrimSpace(walletRef) walletRef = strings.TrimSpace(walletRef)
organizationRef = strings.TrimSpace(organizationRef) organizationRef = strings.TrimSpace(organizationRef)
if walletRef == "" || organizationRef == "" { if walletRef == "" || organizationRef == "" {
//nolint:nilnil // Missing route keys mean no cached route.
return nil, nil return nil, nil
} }
route, err := a.routes.Get(ctx, organizationRef, walletRef) route, err := a.routes.Get(ctx, organizationRef, walletRef)
if err != nil { if err != nil {
if errors.Is(err, merrors.ErrNoData) { if errors.Is(err, merrors.ErrNoData) {
//nolint:nilnil // Route not found in cache.
return nil, nil return nil, nil
} }
return nil, err return nil, err

View File

@@ -129,7 +129,7 @@ func (a *WalletAPI) initDiscoveryClient(cfg *eapi.Config) error {
if err != nil { if err != nil {
return err return err
} }
client, err := discovery.NewClient(a.logger, broker, nil, string(a.Name())) client, err := discovery.NewClient(a.logger, broker, nil, a.Name())
if err != nil { if err != nil {
return err return err
} }

View File

@@ -9,8 +9,8 @@ import (
) )
// generate translations // generate translations
// go:generate Users/stephandeshevikh/go/bin/go18n extract //go:generate Users/stephandeshevikh/go/bin/go18n extract
// go:generate Users/stephandeshevikh/go/bin/go18n merge //go:generate Users/stephandeshevikh/go/bin/go18n merge
// lint go code // lint go code
// docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run -v --timeout 10m0s --enable-all -D ireturn -D wrapcheck -D varnamelen -D tagliatelle -D nosnakecase -D gochecknoglobals -D nlreturn -D stylecheck -D lll -D wsl -D scopelint -D varcheck -D exhaustivestruct -D golint -D maligned -D interfacer -D ifshort -D structcheck -D deadcode -D godot -D depguard -D tagalign // docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run -v --timeout 10m0s --enable-all -D ireturn -D wrapcheck -D varnamelen -D tagliatelle -D nosnakecase -D gochecknoglobals -D nlreturn -D stylecheck -D lll -D wsl -D scopelint -D varcheck -D exhaustivestruct -D golint -D maligned -D interfacer -D ifshort -D structcheck -D deadcode -D godot -D depguard -D tagalign

View File

@@ -0,0 +1,47 @@
version: "2"
linters:
default: none
enable:
- bodyclose
- canonicalheader
- copyloopvar
- durationcheck
- errcheck
- errchkjson
- errname
- errorlint
- gosec
- govet
- ineffassign
- nilerr
- nilnesserr
- nilnil
- noctx
- rowserrcheck
- sqlclosecheck
- staticcheck
- unconvert
- wastedassign
disable:
- depguard
- exhaustruct
- gochecknoglobals
- gochecknoinits
- gomoddirectives
- wrapcheck
- cyclop
- dupl
- funlen
- gocognit
- gocyclo
- ireturn
- lll
- mnd
- nestif
- nlreturn
- noinlineerr
- paralleltest
- tagliatelle
- testpackage
- varnamelen
- wsl_v5

View File

@@ -51,14 +51,14 @@ require (
github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.15.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
) )

View File

@@ -201,8 +201,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -216,16 +216,16 @@ golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210615035016-665e8c7367d1/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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -234,8 +234,8 @@ 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@@ -245,8 +245,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -28,6 +28,7 @@ func (s *service) Load(path string) (*Config, error) {
return nil, merrors.InvalidArgument("config path is required", "path") return nil, merrors.InvalidArgument("config path is required", "path")
} }
//nolint:gosec // Configuration file path is provided by service startup configuration.
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
s.logger.Error("Failed to read config file", zap.String("path", path), zap.Error(err)) s.logger.Error("Failed to read config file", zap.String("path", path), zap.Error(err))

View File

@@ -108,7 +108,7 @@ func (s *service) Start(ctx context.Context) {
if runCtx == nil { if runCtx == nil {
runCtx = context.Background() runCtx = context.Background()
} }
runCtx, s.cancel = context.WithCancel(runCtx) runCtx, s.cancel = context.WithCancel(runCtx) //nolint:gosec // canceled by Stop; service lifecycle outlives Start scope
for i := 0; i < s.cfg.WorkerConcurrency; i++ { for i := 0; i < s.cfg.WorkerConcurrency; i++ {
workerID := "worker-" + strconv.Itoa(i+1) workerID := "worker-" + strconv.Itoa(i+1)
@@ -143,6 +143,10 @@ func (s *service) runWorker(ctx context.Context, workerID string) {
now := time.Now().UTC() now := time.Now().UTC()
task, err := s.tasks.LockNextTask(ctx, now, workerID, s.cfg.LockTTL) task, err := s.tasks.LockNextTask(ctx, now, workerID, s.cfg.LockTTL)
if err != nil { if err != nil {
if errors.Is(err, merrors.ErrNoData) {
time.Sleep(s.cfg.WorkerPoll)
continue
}
s.logger.Warn("Failed to lock next task", zap.String("worker_id", workerID), zap.Error(err)) s.logger.Warn("Failed to lock next task", zap.String("worker_id", workerID), zap.Error(err))
time.Sleep(s.cfg.WorkerPoll) time.Sleep(s.cfg.WorkerPoll)
continue continue

View File

@@ -106,7 +106,7 @@ func (s *service) Start(ctx context.Context) {
if runCtx == nil { if runCtx == nil {
runCtx = context.Background() runCtx = context.Background()
} }
runCtx, s.cancel = context.WithCancel(runCtx) runCtx, s.cancel = context.WithCancel(runCtx) //nolint:gosec // canceled by Stop; service lifecycle outlives Start scope
s.wg.Add(1) s.wg.Add(1)
go func() { go func() {

View File

@@ -14,6 +14,7 @@ type service struct {
// New creates retry policy service. // New creates retry policy service.
func New() Policy { func New() Policy {
//nolint:gosec // Backoff jitter is non-cryptographic and only needs pseudo-random distribution.
return &service{rnd: rand.New(rand.NewSource(time.Now().UnixNano()))} return &service{rnd: rand.New(rand.NewSource(time.Now().UnixNano()))}
} }

View File

@@ -154,6 +154,7 @@ func (i *Imp) Start() error {
runCtx, cancel := context.WithCancel(context.Background()) runCtx, cancel := context.WithCancel(context.Background())
i.runCancel = cancel i.runCancel = cancel
defer cancel()
i.ingest.Start(runCtx) i.ingest.Start(runCtx)
i.delivery.Start(runCtx) i.delivery.Start(runCtx)
i.opServer.SetStatus(health.SSRunning) i.opServer.SetStatus(health.SSRunning)

View File

@@ -379,7 +379,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
candidates, err := mutil.GetObjects[taskDoc](ctx, r.logger, query, nil, r.repo) candidates, err := mutil.GetObjects[taskDoc](ctx, r.logger, query, nil, r.repo)
if err != nil { if err != nil {
if errors.Is(err, merrors.ErrNoData) { if errors.Is(err, merrors.ErrNoData) {
return nil, nil return nil, merrors.ErrNoData
} }
return nil, merrors.InternalWrap(err, "callbacks task query failed") return nil, merrors.InternalWrap(err, "callbacks task query failed")
} }
@@ -418,7 +418,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
return mapTaskDoc(locked), nil return mapTaskDoc(locked), nil
} }
return nil, nil return nil, merrors.ErrNoData
} }
func (r *taskStore) MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error { func (r *taskStore) MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error {

View File

@@ -1,196 +1,47 @@
# See the dedicated "version" documentation section.
version: "2" version: "2"
linters: linters:
# Default set of linters. default: none
# The value can be:
# - `standard`: https://golangci-lint.run/docs/linters/#enabled-by-default
# - `all`: enables all linters by default.
# - `none`: disables all linters by default.
# - `fast`: enables only linters considered as "fast" (`golangci-lint help linters --json | jq '[ .[] | select(.fast==true) ] | map(.name)'`).
# Default: standard
default: all
# Enable specific linter.
enable: enable:
- arangolint
- asasalint
- asciicheck
- bidichk
- bodyclose - bodyclose
- canonicalheader - canonicalheader
- containedctx
- contextcheck
- copyloopvar - copyloopvar
- cyclop
- decorder
- dogsled
- dupl
- dupword
- durationcheck - durationcheck
- embeddedstructfieldcheck
- err113
- errcheck - errcheck
- errchkjson - errchkjson
- errname - errname
- errorlint - errorlint
- exhaustive
- exptostd
- fatcontext
- forbidigo
- forcetypeassert
- funcorder
- funlen
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gochecksumtype
- gocognit
- goconst
- gocritic
- gocyclo
- godoclint
- godot
- godox
- goheader
- gomodguard
- goprintffuncname
- gosec - gosec
- gosmopolitan
- govet - govet
- grouper
- iface
- importas
- inamedparam
- ineffassign - ineffassign
- interfacebloat
- intrange
- iotamixing
- ireturn
- lll
- loggercheck
- maintidx
- makezero
- mirror
- misspell
- mnd
- modernize
- musttag
- nakedret
- nestif
- nilerr - nilerr
- nilnesserr - nilnesserr
- nilnil - nilnil
- nlreturn
- noctx - noctx
- noinlineerr
- nolintlint
- nonamedreturns
- nosprintfhostport
- paralleltest
- perfsprint
- prealloc
- predeclared
- promlinter
- protogetter
- reassign
- recvcheck
- revive
- rowserrcheck - rowserrcheck
- sloglint
- spancheck
- sqlclosecheck - sqlclosecheck
- staticcheck - staticcheck
- tagalign
- tagliatelle
- testableexamples
- testifylint
- testpackage
- thelper
- tparallel
- unconvert - unconvert
- unparam
- unqueryvet
- unused
- usestdlibvars
- usetesting
- varnamelen
- wastedassign - wastedassign
- whitespace
- wsl_v5
- zerologlint
# Disable specific linters.
disable: disable:
- depguard - depguard
- exhaustruct - exhaustruct
- gochecknoglobals - gochecknoglobals
- gochecknoinits
- gomoddirectives - gomoddirectives
- wrapcheck - wrapcheck
- wsl - cyclop
# All available settings of specific linters. - dupl
# See the dedicated "linters.settings" documentation section. - funlen
settings: - gocognit
wsl_v5: - gocyclo
allow-first-in-block: true - ireturn
allow-whole-block: false - lll
branch-max-lines: 2 - mnd
- nestif
# Defines a set of rules to ignore issues. - nlreturn
# It does not skip the analysis, and so does not ignore "typecheck" errors. - noinlineerr
exclusions: - paralleltest
# Mode of the generated files analysis. - tagliatelle
# - testpackage
# - `strict`: sources are excluded by strictly following the Go generated file convention. - varnamelen
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$` - wsl_v5
# This line must appear before the first non-comment, non-blank text in the file.
# https://go.dev/s/generatedcode
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
# - `disable`: disable the generated files exclusion.
#
# Default: strict
generated: lax
# Log a warning if an exclusion rule is unused.
# Default: false
warn-unused: true
# Predefined exclusion rules.
# Default: []
presets:
- comments
- std-error-handling
- common-false-positives
- legacy
# Excluding configuration per-path, per-linter, per-text and per-source.
rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- funlen
- gocyclo
- errcheck
- dupl
- gosec
# Run some linter only for test files by excluding its issues for everything else.
- path-except: _test\.go
linters:
- forbidigo
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via `nolint` comments.
# `/` will be replaced by the current OS file path separator to properly work on Windows.
- path: internal/hmac/
text: "weak cryptographic primitive"
linters:
- gosec
# Exclude some `staticcheck` messages.
- linters:
- staticcheck
text: "SA9003:"
# Exclude `lll` issues for long lines with `go:generate`.
- linters:
- lll
source: "^//go:generate "
# Which file paths to exclude: they will be analyzed, but issues from them won't be reported.
# "/" will be replaced by the current OS file path separator to properly work on Windows.
# Default: []
paths: []
# Which file paths to not exclude.
# Default: []
paths-except: []

View File

@@ -42,12 +42,12 @@ require (
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.48.0 // indirect golang.org/x/crypto v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 // indirect
google.golang.org/grpc v1.79.1 // indirect google.golang.org/grpc v1.79.2 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.11 // indirect
) )

View File

@@ -168,8 +168,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
@@ -183,16 +183,16 @@ golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20210615035016-665e8c7367d1/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-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.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.42.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -210,8 +210,8 @@ gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171 h1:ggcbiqK8WWh6l1dnltU4BgWGIGo+EVYxCaAPih/zQXQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260226221140-a57be14db171/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -31,6 +31,7 @@ func Load(path string) (*Config, error) {
return nil, merrors.InvalidArgument("config: path is empty") return nil, merrors.InvalidArgument("config: path is empty")
} }
//nolint:gosec // config path is provided by process startup arguments/config.
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, merrors.InternalWrap(err, "config: failed to read file") return nil, merrors.InternalWrap(err, "config: failed to read file")
@@ -73,8 +74,10 @@ func Load(path string) (*Config, error) {
} }
if _, ok := sourceSet[driver]; !ok { if _, ok := sourceSet[driver]; !ok {
return nil, merrors.InvalidArgument( //nolint:lll return nil, merrors.InvalidArgument(
"config: pair references unknown source: "+driver.String(), "pairs."+driver.String()) "config: pair references unknown source: "+driver.String(),
"pairs."+driver.String(),
)
} }
processed := make([]PairConfig, len(pairList)) processed := make([]PairConfig, len(pairList))
@@ -86,8 +89,10 @@ func Load(path string) (*Config, error) {
pair.Symbol = strings.TrimSpace(pair.Symbol) pair.Symbol = strings.TrimSpace(pair.Symbol)
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" { if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
return nil, merrors.InvalidArgument( //nolint:lll return nil, merrors.InvalidArgument(
"config: pair entries must define base, quote, and symbol", "pairs."+driver.String()) "config: pair entries must define base, quote, and symbol",
"pairs."+driver.String(),
)
} }
if strings.TrimSpace(pair.Provider) == "" { if strings.TrimSpace(pair.Provider) == "" {

Some files were not shown because too many files have changed in this diff Show More